分块一:
给出一个长为n的数列,以及n个操作,操作涉及区间加法,单点查值。
普通分块水过
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> using namespace std; int n, a[100100], len,tag[100100], posit[100100]; void change(int l, int r, int c) { if (posit[l]==posit[r]) { for (int i = l; i <= r; i++) { a[i] += c; } return; } for(int i=l; i<=posit[l]*len; i++) a[i] += c; for(int i=posit[l] + 1; i<= posit[r]-1; i++) tag[i] += c; for(int i=(posit[r] - 1)*len +1;i <= r; i++) a[i] += c; } int main() { scanf("%d", &n); len = sqrt(n); for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); posit[i] = (i - 1) / len + 1; } int opt, l, r, c; while (n--) { scanf("%d%d%d%d", &opt, &l, &r, &c); if (!opt) { change(l,r,c); } else { printf("%d ",a[r]+tag[posit[r]]); } } return 0; }
分块二:
给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的元素个数。
块内sort,lower_bound,加一个数组,辅助残块sort,因为原序列不能变
#include<cstdio> #include<iostream> #include<cstdlib> #include<algorithm> #include<cmath> #define int long long #define MAXN 100000 using namespace std; int n,opt,a[MAXN],b[MAXN],pos[MAXN],tag[MAXN],len; void change(int l,int r,int c) { if(pos[l]==pos[r]) { for(int i=l;i<=r;i++)a[i]+=c; for(int i=(pos[l]-1)*len+1;i<=min(pos[l]*len,n);i++)b[i]=a[i]; sort(b+(pos[l]-1)*len+1,b+1+min(n,pos[l]*len)); return; } for(int i=l;i<=pos[l]*len;i++)a[i]+=c; for(int i=(pos[l]-1)*len+1;i<=pos[l]*len;i++)b[i]=a[i]; sort(b+(pos[l]-1)*len+1,b+1+pos[l]*len); for(int i=pos[l]+1;i<=pos[r]-1;i++)tag[i]+=c; for(int i=(pos[r]-1)*len+1;i<=r;i++)a[i]+=c; for(int i=(pos[r]-1)*len+1;i<=min(pos[r]*len,n);i++)b[i]=a[i]; sort(b+(pos[r]-1)*len+1,b+1+min(pos[r]*len,n)); } int query(int l,int r,int c) { int ans=0; if(pos[l]==pos[r]) { for(int i=l;i<=r;i++) { if(a[i]+tag[pos[i]]<c*c)ans++; } return ans; } for(int i=l;i<=pos[l]*len;i++) { if(a[i]+tag[pos[i]]<c*c)ans++; } for(int i=pos[l]+1;i<=pos[r]-1;i++)ans+=lower_bound(b+(i-1)*len+1,b+(i*len)+1,c*c-tag[i])-b-((i-1)*len+1); for(int i=(pos[r]-1)*len+1;i<=min(n,r);i++) { if(a[i]+tag[pos[i]]<c*c)ans++; } return ans; } signed main() { scanf("%lld",&n); len=sqrt(n); for(int i=1;i<=n;i++)pos[i]=(i-1)/len+1; for(int i=1;i<=n;i++) { scanf("%lld",&a[i]); b[i]=a[i]; } for(int i=1;i<=pos[n];i++)sort(b+(i-1)*len+1,b+1+min(i*len,n)); for(int i=1,l,r,c;i<=n;i++) { scanf("%lld%lld%lld%lld",&opt,&l,&r,&c); if(opt==0) { change(l,r,c); } if(opt==1) { printf("%lld ",query(l,r,c)); } } return 0; }
分块三:
给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的前驱(比其小的最大元素)。
块内sort加lower_bound查询
#include <cstdio> #include <iostream> #include <cmath> #include <algorithm> #define N 500500 using namespace std; inline long long read() { int x = 0, f = 1; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s >= '0' && s <= '9') { x = x * 10 + s - '0'; s = getchar(); } return x * f; } int n, len; int a[N], b[N]; int tag[N], lz[N]; int L[N], R[N]; inline void change(int l, int r, int c) { if (tag[l] == tag[r]) { for (int i = l; i <= r; i++) a[i] += c; for (int i = L[tag[l]]; i <= R[tag[r]]; i++) b[i] = a[i]; sort(b + L[tag[l]], b + R[tag[l]] + 1); return; } for (int i = l; i <= R[tag[l]]; i++) a[i] += c; for (int i = L[tag[l]]; i <= R[tag[l]]; i++) b[i] = a[i]; sort(b + L[tag[l]], b + R[tag[l]] + 1); for (int i = tag[l] + 1; i < tag[r]; i++) lz[i] += c; for (int i = L[tag[r]]; i <= r; i++) a[i] += c; for (int i = L[tag[r]]; i <= R[tag[r]]; i++) b[i] = a[i]; sort(b + L[tag[r]], b + R[tag[r]] + 1); } inline int ask(int l, int r, int c) { int ans = -1; if (tag[l] == tag[r]) { for (int i = l; i <= r; i++) if (a[i] + lz[tag[i]] < c) ans = max(ans, a[i] + lz[tag[i]]); return ans; } for (int i = l; i <= R[tag[l]]; i++) if (a[i] + lz[tag[i]] < c) ans = max(ans, a[i] + lz[tag[i]]); for (int i = tag[l] + 1; i < tag[r]; i++) { int k = lower_bound(b + L[i], b + R[i] + 1, c - lz[i]) - b; if (k != L[i]) if (b[k - 1] + lz[i] < c) ans = max(ans, b[k - 1] + lz[i]); } for (int i = L[tag[r]]; i <= r; i++) if (a[i] + lz[tag[i]] < c) ans = max(ans, a[i] + lz[tag[i]]); return ans; } int main() { n = read(); int len = sqrt(n); for (int i = 1; i <= n; i++) tag[i] = (i - 1) / len + 1; for (int i = 1; i <= tag[n]; i++) L[i] = R[i - 1] + 1, R[i] = min(n, L[i] + len - 1); for (int i = 1; i <= n; i++) a[i] = read(), b[i] = a[i]; for (int i = 1; i <= tag[n]; i++) sort(b + L[i], b + R[i] + 1); for (int i = 1; i <= n; i++) { int opt = read(), l = read(), r = read(), c = read(); if (opt == 0) change(l, r, c); else printf("%d ", ask(l, r, c)); } return 0; }
分块四:
给出一个长为n的数列,以及n个操作,操作涉及区间加法,区间求和。
打标记,其余暴力
#include<cstdio> #include<iostream> #include<cstdlib> #include<iostream> #include<cmath> #define MAXN 1000010 #define int long long int a[MAXN],tag[MAXN],le[MAXN],ri[MAXN],sum[MAXN],val[MAXN],pos[MAXN],len; void change(int l,int r,int c) { if(pos[l]==pos[r]) { for(int i=l;i<=r;i++) { a[i]+=c;val[pos[i]]+=c; } return; } for(int i=l;i<=ri[pos[l]];i++) a[i]+=c , val[pos[i]]+=c; for(int i=pos[l]+1;i <= pos[r]-1 ; i++)tag[i]+=c; for(int i=le[pos[r]];i<=r;i++) a[i]+=c , val[pos[i]]+=c; } int query(int l,int r,int c) { int mod=c+1,ans=0; if(pos[l]==pos[r]) { for(int i=l;i<=r;i++)ans+=(a[i]+tag[pos[i]]),ans%=mod; return ans; } for(int i=l;i<=ri[pos[l]];i++)ans+=(a[i]+tag[pos[i]]),ans%=mod; for(int i=pos[l]+1;i<=pos[r]-1;i++) { ans+=(val[i]+tag[i]*len); ans%=mod; } for(int i=le[pos[r]];i<=r;i++)ans+=(a[i]+tag[pos[i]]),ans%=mod; return ans; } signed main() { int n; scanf("%lld",&n); len=sqrt(n); for(int i=1;i<=n;i++) { pos[i]=(i-1)/len+1; scanf("%lld",&a[i]); sum[i]=sum[i-1]+a[i]; } for(int i=1;i<=pos[n];i++) { le[i]=(i-1)*len+1; ri[i]=i*len; val[i]=sum[ri[i]]-sum[le[i]-1]; } for(int i=1,opt,l,r,c;i<=n;i++) { scanf("%lld%lld%lld%lld",&opt,&l,&r,&c); if(opt==0)change(l,r,c); if(opt==1) { printf("%lld ",query(l,r,c)); } } return 0; }
分块五:
给出一个长为n的数列,以及n个操作,操作涉及区间开方,区间求和。
线段树,标记维护区间是否都为一
#include<cstdio> #include<iostream> #include<cstdlib> #include<cmath> #define MAXN 5100000 #define int long long using namespace std; long long tr[MAXN*4],n,a[MAXN*2]; bool vis[MAXN*4]; void updata(int k) { tr[k]=tr[k<<1]+tr[k<<1|1]; if(vis[k<<1]==1 && vis[k<<1|1]==1)vis[k]=1; return; } void change(int k,int l,int r,int x,int y) { if(vis[k])return; if(l>=x && r<=y && l==r) { tr[k]=sqrt(tr[k]); if(tr[k]==1||tr[k]==0)vis[k]=1; return; } int mid=(l+r)>>1; if(x<=mid)change(k<<1,l,mid,x,y); if(y>mid)change(k<<1|1,mid+1,r,x,y); updata(k); } int ask(int k,int l,int r,int x,int y) { if(l>=x && r<=y)return tr[k]; int mid=(l+r)>>1,ans=0; if(x<=mid)ans+=ask(k<<1,l,mid,x,y); if(y>mid)ans+=ask(k<<1|1,mid+1,r,x,y); return ans; } void build(int k,int l,int r) { if(l==r) { tr[k]=a[l]; if(a[l]==1||a[l]==0)vis[k]=1; return ; } int mid=(l+r)>>1; build(k<<1,l,mid); build(k<<1|1,mid+1,r); updata(k); } signed main() { scanf("%lld",&n); for(int i=1;i<=n;i++)scanf("%lld",&a[i]); build(1,1,n); for(int i=1,opt,l,c,r;i<=n;i++) { scanf("%lld%lld%lld%lld",&opt,&l,&r,&c); if(l>r)swap(l,r); if(opt==0) { change(1,1,n,l,r); } else { printf("%lld ",ask(1,1,n,l,r)); } } return 0; }
分块六:
给出一个长为n的数列,以及n个操作,操作涉及单点插入,单点询问,数据随机生成。
开一个vector 插入,pair ,
先说随机数据的情况
之前提到过,如果我们块内用数组以外的数据结构,能够支持其它不一样的操作,比如此题每块内可以放一个动态的数组,每次插入时先找到位置所在的块,再暴力插入,把块内的其它元素直接向后移动一位,当然用链表也是可以的。
查询的时候类似,复杂度分析略。
但是这样做有个问题,如果数据不随机怎么办?
如果先在一个块有大量单点插入,这个块的大小会大大超过√n,那块内的暴力就没有复杂度保证了。
还需要引入一个操作:重新分块(重构)
每根号n次插入后,重新把数列平均分一下块,重构需要的复杂度为O(n),重构的次数为√n,所以重构的复杂度没有问题,而且保证了每个块的大小相对均衡。
当然,也可以当某个块过大时重构,或者只把这个块分成两半。
#include<cstdio> #include<cstdlib> #include<iostream> #include<cmath> #include<vector> #define N 200000 using namespace std; int pos[N],len,le[N],ri[N],n,a[N],st[N],m; vector <int> ve[N]; pair <int,int> query(int b) { int x=1; while(b>ve[x].size()) { b-=ve[x].size(); x++; } return make_pair(x,b-1); } void rebuild() { int top=1; for(int i=1;i<=m;i++) { for(vector<int> ::iterator j=ve[i].begin();j!=ve[i].end();j++) st[++top]=*j; ve[i].clear(); } int le=sqrt(top); for(int i=1;i<=top;i++)ve[(i-1)/le+1].push_back(st[i]); m=(top-1)/le+1; } void insert(int a,int b) { pair <int,int> t=query(a); ve[t.first].insert( ve[t.first].begin() + t.second ,b); // if(ve[t.first].size()>20*len) rebuild(); } int main() { // freopen("a9.in","r",stdin); // freopen("ans.out","w",stdout); scanf("%d",&n); len=sqrt(n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=1;i<=n;i++) ve[(i-1)/len+1].push_back(a[i]); int m=(n-1)/len+1; for(int i=1,opt,x,y,c;i<=n;i++) { scanf("%d%d%d%d",&opt,&x,&y,&c); if(opt==0) { insert(x,y); } if(opt==1) { pair <int,int> t = query(y); printf("%d ",ve[t.first][t.second]); } } return 0; }
分块七:
给出一个长为n的数列,以及n个操作,操作涉及区间乘法,区间加法,单点询问。
很显然,如果只有区间乘法,和分块入门 1 的做法没有本质区别,但要思考如何同时维护两种标记。
我们让乘法标记的优先级高于加法(如果反过来的话,新的加法标记无法处理)
若当前的一个块乘以m1后加上a1,这时进行一个乘m2的操作,则原来的标记变成m1*m2,a1*m2
若当前的一个块乘以m1后加上a1,这时进行一个加a2的操作,则原来的标记变成m1,a1+a2
#include<map> #include<set> #include<cmath> #include<stack> #include<queue> #include<cstdio> #include<vector> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define mod 10007 #define pi acos(-1) #define inf 0x7fffffff #define ll long long using namespace std; ll read() { ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int n,blo; int v[100005],bl[100005],atag[1005],mtag[1005]; void reset(int x) { for(int i=(x-1)*blo+1;i<=min(n,x*blo);i++) v[i]=(v[i]*mtag[x]+atag[x])%mod; atag[x]=0;mtag[x]=1; } void solve(int f,int a,int b,int c) { reset(bl[a]); for(int i=a;i<=min(bl[a]*blo,b);i++) { if(f==0)v[i]+=c; else v[i]*=c; v[i]%=mod; } if(bl[a]!=bl[b]) { reset(bl[b]); for(int i=(bl[b]-1)*blo+1;i<=b;i++) { if(f==0)v[i]+=c; else v[i]*=c; v[i]%=mod; } } for(int i=bl[a]+1;i<=bl[b]-1;i++) { if(f==0)atag[i]=(atag[i]+c)%mod; else { atag[i]=(atag[i]*c)%mod; mtag[i]=(mtag[i]*c)%mod; } } } int main() { n=read();blo=sqrt(n); for(int i=1;i<=n;i++)v[i]=read(); for(int i=1;i<=n;i++)bl[i]=(i-1)/blo+1; for(int i=1;i<=bl[n];i++)mtag[i]=1; for(int i=1;i<=n;i++) { int f=read(),a=read(),b=read(),c=read(); if(f==2)printf("%d ",(v[b]*mtag[bl[b]]+atag[bl[b]])%mod); else solve(f,a,b,c); } return 0; }
分块八:
给出一个长为n的数列,以及n个操作,操作涉及区间询问等于一个数c的元素,并将这个区间的所有元素改为c。
区间修改没有什么难度,这题难在区间查询比较奇怪,因为权值种类比较多,似乎没有什么好的维护方法。
模拟一些数据可以发现,询问后一整段都会被修改,几次询问后数列可能只剩下几段不同的区间了。
我们思考这样一个暴力,还是分块,维护每个分块是否只有一种权值,区间操作的时候,对于同权值的一个块就O(1)统计答案,否则暴力统计答案,并修改标记,不完整的块也暴力。
这样看似最差情况每次都会耗费O(n)的时间,但其实可以这样分析:
假设初始序列都是同一个值,那么查询是O(√n),如果这时进行一个区间操作,它最多破坏首尾2个块的标记,所以只能使后面的询问至多多2个块的暴力时间,所以均摊每次操作复杂度还是O(√n)。
换句话说,要想让一个操作耗费O(n)的时间,要先花费√n个操作对数列进行修改。
初始序列不同值,经过类似分析后,就可以放心的暴力啦。
#include<cstdio> #include<iostream> #include<cmath> #include<cstring> using namespace std; const int N = 1000000; int pos[N],le[N],ri[N],cnt,n,a[N],tag[N],len; void reset(int x) { if(tag[x]==-1)return; else { for(int i=le[x];i<=ri[x];i++) a[i]=tag[x]; } tag[x]=-1; } void change(int l,int r,int c) { int ans=0; reset(pos[l]); for(int i=l;i<=min(ri[pos[l]],r);i++) { if(a[i]!=c)a[i]=c; else ans++; } if(pos[l]!=pos[r]) { reset(pos[r]); for(int i=le[pos[r]];i<=r;i++) { if(a[i]!=c)a[i]=c; else ans++; } } for(int i=pos[l]+1;i<=pos[r]-1;i++) { if(tag[i]!=-1) { if(tag[i]!=c)tag[i]=c; else ans+=len; } else { for(int j=le[i];j<=ri[i];j++) { if(a[j]!=c)a[j]=c; else ans++; tag[i]=c; } } } printf("%d ",ans); } int main() { scanf("%d",&n); len=sqrt(n); memset(tag,-1,sizeof(tag)); for(int i=1;i<=n;i++)scanf("%d",&a[i]); for(int i=1;i<=n;i++)pos[i]=(i-1)/len+1; for(int i=1;i<=pos[n];i++) { le[i]=(i-1)*len+1; ri[i]=min(n,i*len); } for(int i=1,l,r,c;i<=n;i++) { scanf("%d%d%d",&l,&r,&c); change(l,r,c); } return 0; }
分块九:
给出一个长为n的数列,以及n个操作,操作涉及询问区间的最小众数。 不带修改,要是带呢?
#include<map> #include<set> #include<cmath> #include<stack> #include<queue> #include<cstdio> #include<vector> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define mod 10007 #define pi acos(-1) #define inf 0x7fffffff #define ll long long using namespace std; ll read() { ll x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int n,blo,id; int v[100005],bl[100005]; int f[605][605]; map<int,int>mp; int val[100005],cnt[100005]; vector<int>ve[100005]; void pre(int x) { memset(cnt,0,sizeof(cnt)); int mx=0,ans=0; for(int i=(x-1)*blo+1;i<=n;i++) { cnt[v[i]]++; int t=bl[i]; if(cnt[v[i]]>mx||(cnt[v[i]]==mx&&val[v[i]]<val[ans])) ans=v[i],mx=cnt[v[i]]; f[x][t]=ans; } } int query(int l,int r,int x) { int t=upper_bound(ve[x].begin(),ve[x].end(),r)-lower_bound(ve[x].begin(),ve[x].end(),l); return t; } int query(int a,int b) { int ans,mx; ans=f[bl[a]+1][bl[b]-1]; mx=query(a,b,ans); for(int i=a;i<=min(bl[a]*blo,b);i++) { int t=query(a,b,v[i]); if(t>mx||(t==mx&&val[v[i]]<val[ans]))ans=v[i],mx=t; } if(bl[a]!=bl[b]) for(int i=(bl[b]-1)*blo+1;i<=b;i++) { int t=query(a,b,v[i]); if(t>mx||(t==mx&&val[v[i]]<val[ans]))ans=v[i],mx=t; } return ans; } int main() { n=read(); blo=200; for(int i=1;i<=n;i++) { v[i]=read(); if(!mp[v[i]]) { mp[v[i]]=++id; val[id]=v[i]; } v[i]=mp[v[i]]; ve[v[i]].push_back(i); } for(int i=1;i<=n;i++)bl[i]=(i-1)/blo+1; for(int i=1;i<=bl[n];i++)pre(i); for(int i=1;i<=n;i++) { int a=read(),b=read(); if(a>b)swap(a,b); printf("%d ",val[query(a,b)]); } return 0; }
分块--好爽