2018 10 02 校內模擬 字首和+二分+線段樹,廣義尤拉定理
T1:聚會 party.cpp 【描述】
在成都的一條街道上,一共有 N 戶人家,每個家庭有 Xi 個人,他們和諧的生活在 一起,作為全國和諧街道,他們經常會小範圍組織活動,每次活動會選擇一戶作為聚點, 並要求某些家庭參加,為了方便通知,村長每次邀請位置連續的家庭。因為每戶人數不 同,每個家庭之間有一定距離,村長希望你計算出每次邀請的家庭的移動代價。第 i 個家 庭移動到家庭j的代價是: Xi*dis(i,j) (dis(i,j)表示i到j的距離) 村長一共安排了m次聚會,每次邀請[Li,Ri]的家庭參加 【輸入】 第一行兩個數表示 n,m 第二行n-1 個數,第 i個數表示第i 個家庭與第 i+1 個家庭的距離 Di
第三行n 個數,表示每個家庭的人數 Xi
之後 m行每行三個數 x l r,表示查詢要把區間 [l,r]的家庭移動到x點的代價和 【輸出】
對於每個詢問輸出一個數表示答案,對19260817取模 【輸入樣例】 5 5 2 3 4 5 1 2 3 4 5 1 1 5 3 1 5 2 3 3 3 3 3 1 5 5 【輸出樣例】 5 5 2 3 4 5 1 2 3 4 5 1 1 5 3 1 5 2 3 3 3 3 3 1 5 5 【子任務】 對於30%的資料, n,m≤1000 對於另外20%的資料,所有家庭間的距離都為 1 對於另外20%的資料,所有家庭人數都為 1 對於100%的資料 , n,m≤200000;Xi,Di <=2*10^9
分析: 我們可以發現,距離計算公式 Xidis(i,j) =Xi(|dis(i,1)-dis(j,1)|) =|Xidis(i,1)-Xidis(j,1)| 所以我們可以維護字首和來快速計算某個家庭到目標點的路程。 對於連續的一段家庭,有下面三種情況: 1.目標點在左邊,此時S=X[l] * dis(l,1)-X[l] * dis(pos,1)+ X[l+1]* dis(l+1,1)-X[l+1] * dis(pos,1)+…+X[r] * dis(r,1)-X[r] * dis(pos,1)=sigma(x*dis(x,1))-sigma(X)dis(pos,1) 2.目標點在右邊,形同情況1 3.目標點在中間,則情況1,2各考慮一次。 字首和維護sigma(X)和sigma(x
程式碼:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=19260817;
const int N=200010;
int n,m;
ll d[N],a[N];
ll apre[N],pre[N],sumpre[N];
ll work(int x,int l,int r,bool left)
{
if(l>r) return 0;
ll ans1=((apre[r]-apre[l-1])%mod+mod)%mod;
ans1=ans1*pre[x]%mod;
ll ans2=((sumpre[r]-sumpre[l-1])%mod+mod)%mod;
if(!left) swap(ans1,ans2);
return ((ans1-ans2)%mod+mod)%mod;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=2;i<=n;i++)
{
scanf("%lld",&d[i]);
pre[i]=(pre[i-1]+(d[i]%=mod))%mod;
}
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
apre[i]=(apre[i-1]+(a[i]%=mod))%mod;
sumpre[i]=(sumpre[i-1]+a[i]*pre[i]%mod)%mod;
}
for(int i=1,x,l,r;i<=m;i++)
{
scanf("%d%d%d",&x,&l,&r);
ll ans1=work(x,l,min(r,x-1),true);
ll ans2=work(x,max(l,x+1),r,false);
printf("%lld\n",(ans1+ans2)%mod);
}
return 0;
}
T2:矩陣分組 matrix.cpp 【描述】 有 N 行 M 列的矩陣,每個格子中有一個數字,現在需要你將格子的數字分為 A,B 兩部分 要求: 1、每個數字恰好屬於兩部分的其中一個部分 2、每個部分內部方塊之間,可以上下左右相互到達,且每個內部方塊之間可以相互到達, 且最多拐一次彎
如: AAAAA AAAAA AAAAA AABAA BaAAA AAABB ABBBA BBAAA AAABB AABAA BaAAA ABBBB AAAAA AAAAA BBBBB
(1) (2) (3)
其中(1)(2)是不允許的分法,(3)是允許的分法。在(2)中,a屬於 A區域,這兩個 a元素之間 互相到達,但是不滿足只拐一次彎到達。
問:對於所有合法的分組中,A 區域和 B 區域的極差,其中極差較大的一個區域最小值是 多少 提示:極差就是區域內最大值減去最小值。 【輸入】 第一行兩個正整數 n,m 接下來n 行,每行 m個自然數A_{i,j}表示權值 【輸出】 輸出一行表示答案 【輸入樣例】 4 4 1 12 6 11 11 4 2 14 10 1 9 20 4 17 13 10 【輸出樣例】 11
【樣例解釋】 1 12 6 11 11 4 2 14 10 1 9 20 4 17 13 10
分法不唯一,如圖是一種合法的分法。左邊部分極差 12-1=11,右邊一塊極差 20-10=10, 所以答案取這兩個中較大者 11。沒有別的分法,可以使答案更小。 【測試資料】 測試點 N,m範圍 1,2 n<=10,m<=10 3-4 n=1,m<=2000 5-7 n<=200,m<=200 8-10 n<=2000,m<=2000 所有權值1<=a_ij<=10^9
分析:我們可以二分答案,然後驗證 結論一:設A區域在左邊,對於A區域,每一行的長度是單調且連續。直觀看下圖 因為要保證只能拐一次彎。 結論二、A和B 可以互換,我們不妨設最大極值在A中,那麼A 這樣一個梯狀的區域的 頂角處於某個角落,我們不妨設為左上角,然後將矩陣做 3 次旋轉就可以考慮到所有情況。
程式碼:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long int
using namespace std;
const int maxn = 2010,maxm = 100005,INF = 2000000000;
inline int read(){
int out = 0,flag = 1;char c = getchar();
while (c < 48 || c > 57) {if (c == '-') flag = -1; c = getchar();}
while (c >= 48 && c <= 57) {out = out * 10 + c - 48; c = getchar();}
return out * flag;
}
int n,m,A[4][maxn][maxn],gmax = -INF,gmin = INF;
void init(){
n = read();
m = read();
int x = 1,x1 = 1,x2 = n,x3 = m,y = 1,y1 = n,y2 = m,y3 = 1,t;
for (int i = 1; i <= n; i++){
for (int j = 1; j <= m; j++){
t = A[0][x][y++] = A[1][x1++][y1] = A[2][x2][y2--] = A[3][x3--][y3] = read();
if (t > gmax) gmax = t;
if (t < gmin) gmin = t;
}
x++; y = 1;
y1--; x1 = 1;
x2--; y2 = m;
y3++; x3 = m;
}
}
int endi[maxn];
bool Check(int u,int d){
if (u & 1) swap(n,m);
endi[0] = m;
for (int i = 1,j; i <= n; i++){
for (j = 1; j <= endi[i - 1]; j++)
if (gmax - A[u][i][j] > d)
break;
endi[i] = j - 1;
T3:序列維護 sequence.cpp 【描述】 沒有題面,非常友好 給出一個長度為 n的序列,每個位置有個數字 Ai,有2個操作: 1、區間修改,將[L,R]區間的數字加上一個數 2、區間查詢[l,r] 查詢 【輸入格式】 第一行兩個整數 n,m ,表示序列長度和運算元。 接下來一行, n個整數a_i 表示這個序列。 接下來m 行,可能是以下兩種操作之一: 1 l,r,x,表示區間[l,r] 加上x 2 l,r,p ,表示進行一次查詢,模數為 p 【輸出格式】 對於每個操作 2,輸出一行表示答案。 【輸入樣例1】 6 4 1 2 3 4 5 6 2 1 2 10000007 2 2 3 5 1 1 4 1 2 2 4 10 【輸出樣例1】 1 3 1 【輸入樣例2】 5 5 2 3 3 3 3 1 1 1 530739835 2 1 1 8356089 2 1 4 5496738 1 1 2 66050181 1 2 4 138625417 【輸出樣例2】 4306230 697527 【資料範圍】 測試點 n的範圍 m的範圍 特殊限制 1 n = 5 m = 5 a_i ≤3 2 n = 1000 m = 1000 查詢區間長度為 1 3 n = 100000 m = 100000 查詢區間長度為 1 4 n = 1000 m = 1000 查詢區間長度不大於 2 5 n = 100000 m = 100000 查詢區間長度不大於 2 6 n = 1000 m = 1000 a_i ≤2 7 n = 1000 m = 1000 p = 2 8 n = 100000 m = 100000 p = 2 無修改 9 n = 1000 m = 1000 p ≤100000 無修改 10 n = 500000 m = 500000m=500000 無
分析:20%單點查詢 20%:兩個單點查詢然後快速冪(注意快速冪是先%不然會爆long long) 20%:單點查詢判斷奇偶性 點1和點6:暴力 100%:線段樹+廣義尤拉定理
所以我們先預處理出尤拉函式 如何判斷指數大於等於φ(p)? 只需要列舉後面 5 項或到第一個 1 即可,這裡暴力計算,當大於等於φ(p)時退出。(因為22222>=2*10^7 , 1^n=1) 當指數大於等於φ(p),遞迴計算 a[l] ^ (l+1,r) %φ(p)+ φ(p)
程式碼:
#include<bits/stdc++.h>
const int N=500005;
const int M=20000005;
const int inf=N-4;
const int lim=20000000;
typedef long long ll;
using namespace std;
int n,m,x,phi[M],prime[M],cnt;
ll c[N],a[N];bool vis[M];
int las[N];
inline int read(){
int f=1,x=0;char ch;
do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
return f*x;
}
inline int fpow(int x,ll p,int yql){
int ans=1;
for(;p;p>>=1,x=1LL*x*x%yql)if(p&1)ans=1LL*ans*x%yql;
return ans;
}
inline void calcpri(){
phi[1]=1;
for(int i=2;i<=lim;i++){
if(!vis[i])prime[++cnt]=i,phi[i]=i-1;
for(int j=1;j<=cnt;j++){
int t=i*prime[j];if(t>lim)break;
vis[t]=1;
if(i%prime[j]==0){phi[t]=phi[i]*prime[j];break;}
phi[t]=phi[i]*(prime[j]-1);
}
}
}
ll sumv[N<<2],addv[N<<2],fone[N<<2];
#define lson (o<<1)
#define rson (o<<1|1)
inline void pushup(int o){
sumv[o]=sumv[lson]+sumv[rson];
fone[o]=min(fone[lson],fone[rson]);
}
inline void build(int o,int l,int r){
fone[o]=inf;
if(l==r){sumv[o]=a[l];if(a[l]==1)fone[o]=l;return;}
int mid=(l+r)>>1;
build(lson,l,mid);build(rson,mid+1,r);
pushup(o);
}
inline void pushdown(int o,int l,int r){
if(!addv[o])return;
int mid=(l+r)>>1;
addv[lson]+=addv[o];addv[rson]+=addv[o];
sumv[lson]+=addv[o]*1LL*(mid-l+1);
sumv[rson]+=addv[o]*1LL*(r-mid);
fone[lson]=inf;fone[rson]=inf;
addv[o]=0;
}
inline void change(int o,int l,int r,int q,int v){
if(l==r){sumv[o]=v;return;}
int mid=(l+r)>>1;
pushdown(o,l,r);
if(q<=mid)change(lson,l,mid,q,v);
else change(rson,mid+1,r,q,v);
pushup(o);
}
inline void optadd(int o,int l,int r,int ql,int qr,int v){
if(ql<=l&&r<=qr){addv[o]+=v;sumv[o]+=1LL*(r-l+1)*v;fone[o]=inf;return;}
int mid=(l+r)>>1;pushdown(o,l,r);
if(ql<=mid)optadd(lson,l,mid,ql,qr,v);
if(qr>mid)optadd(rson,mid+1,r,ql,qr,v);
pushup(o);
}
inline ll querysum(int o,int l,int r,int q){
if(l==r)return sumv[o];
int mid=(l+r)>>1;pushdown(o,l,r);
if(q<=mid)return querysum(lson,l,mid,q);
else return querysum(rson,mid+1,r,q);
}
inline ll query(int q){
if(las[q]==m)return a[q];
las[q]=m;
return a[q]=querysum(1,1,n,q);
}
inline int queryfst(int o,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr)return fone[o];
int mid=(l+r)>>1;pushdown(o,l,r);
int ans=inf;
if(ql<=mid)ans=min(ans,queryfst(lson,l,mid,ql,qr));
if(qr>mid)ans=min(ans,queryfst(rson,mid+1,r,ql,qr));
return ans;
}
inline ll calc(int l,int r,int yql){
if(query(l)%yql==0)return 0;
if(yql==1)return 1;
if(l==r)return query(l)%yql+(query(l)>=yql)*yql;
int f=min(r,l+5);
for(int i=l+1;i<=f;i++)if(query(i)==1){f=i;break;}
ll last=query(f),q=0;
for(int i=f-1;i>=l+1;i--){
q=last,last=1;
while(q--){
last*=query(i);
if(last>=phi[yql])return fpow(query(l)%yql,calc(l+1,r,phi[yql])+phi[yql],yql);
}
}
return fpow(query(l)%yql,last,yql);
}
int main(){
calcpri();memset(las,-1,sizeof(las));
n=read();m=read();
for(int i=1;i<=n;i++)a[i]=read();
build(1,1,n);
while(m--){
int opt=read(),l=read(),r=read(),yql=read();
if(opt==1)optadd(1,1,n,l,r,yql);
else printf("%lld\n",(calc(l,r,yql))%yql);
}
}