分块算法总结
分块,就是一种暴力算法,不过复杂度优于暴力,是基于足够的预处理和合理可行的维护操作进行优化时间,
在预处理+维护的耗时上与暴力处理的耗时上找到一种平衡,于是出了这个优美的算法
标志:查询某一区间内元素种类数,查询某一区间大于等于某一元素的数的个数(即排名
模板:LuoguP2801 教主的魔法
为了查询大于等于C的个数,可以排序,用区间长度-C的排名就是 答案数。
所以可以动态维护一个块内有序的数组
#include#define re register #define inc(i,j,k) for(re int i=j;i<=k;++i) #define il inline using namespace std; const int maxn=1000010; vector <int> ve[1010]; int v[maxn],bl[maxn],atag[maxn]; int n,m,base; il void reset(int x) //重新维护一个块内元素的顺序 { ve[x].clear(); inc(i,(x-1)*base+1,min(x*base,n)) ve[x].push_back(v[i]);//别忘了容易溢出n的数组大小呀 sort(ve[x].begin(),ve[x].end()); } il void add(int a,int b,int c)//区间加 { inc(i,a,min(bl[a]*base,b)) v[i]+=c;//暴力处理前半部分 reset(bl[a]); if(bl[a]!=bl[b]) {inc(i,(bl[b]-1)*base+1,b) v[i]+=c; reset(bl[b]);}//暴力处理后面部分 inc(i,bl[a]+1,bl[b]-1) atag[i]+=c; } il int query(int a,int b,int c)//区间查询 { int ans=0; inc(i,a,min(bl[a]*base,b)) if(v[i]+atag[bl[a]] ; //暴力处理前部分 if(bl[a]!=bl[b]) { inc(i,(bl[b]-1)*base+1,b) if(v[i]+atag[bl[b]] ;//暴力处理后面部分 } inc(i,bl[a]+1,bl[b]-1)//对于块来说,直接减去lazy_tag的影响 { int x=c-atag[i]; ans+=lower_bound(ve[i].begin(),ve[i].end(),x)-ve[i].begin();//查询第一个大于等于x的数的位置 } return ans; } int main() { scanf("%d %d",&n,&m); base=sqrt(n); inc(i,1,n) scanf("%d",&v[i]),bl[i]=(i-1)/base+1,ve[bl[i]].push_back(v[i]); inc(i,1,bl[n]) sort(ve[i].begin(),ve[i].end()); char s; re int x,y,z; inc(i,1,m) { s=getchar(); while(s!='A'&&s!='M') s=getchar(); if(s=='M') { scanf("%d %d %d",&x,&y,&z); add(x,y,z); } else { scanf("%d %d %d",&x,&y,&z); printf("%d\n",y-x+1-query(x,y,z)); } } }
其他例题先咕着吧
然后...
莫队
莫队其实也是基于分块的算法,主要是一种思想吧我认为,基于对询问问题的离线处理按一定顺序去处理问题
然后保证了暴力的复杂度
具体实现是要按左区间的块排序,快相同的按右端点排序,保证同一个块内复杂度O(n),共有sqrt(n)块
所以总复杂度 O(n*sqrt(n))
主要用于查询元素的种类数
模板:LuoguP2709 小B的询问
#include#define ll long long #define re register using namespace std; #define maxn 50005 struct query { int l,r,id,pos; // friend bool operator < (query xx,query yy) // { if(xx.pos==yy.pos) return xx.l bool operator <(const query &x) const {if(pos==x.pos)return r<x.r; else return pos<x.pos;} }a[maxn]; int b[maxn],n,m,K;ll cnt[maxn],Ans[maxn]; 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 x*f; } int main() { // freopen("testdata.in.txt","r",stdin); // freopen("4321.txt","w",stdout); n=read(); m=read(); K=read(); int siz=(int) sqrt(n); for(re int i=1;i<=n;++i) b[i]=read(); for(re int i=1;i<=m;++i) { a[i].l=read(); a[i].r=read(); a[i].id=i; a[i].pos=(a[i].l-1)/siz+1; } sort(a+1,a+m+1); int l=1,r=0; long long ans=0; for(re int i=1;i<=m;++i) { while(l>a[i].l) { l--; cnt[b[l]]++; ans+= 2*cnt[b[l]]-1 ;} //外拓的时候,先加减再统计 while(r2 *cnt[b[r]]-1 ;} while(r>a[i].r) {cnt[b[r]]--; ans-= 2*cnt[b[r]]+1 ; r--; } //回溯的时候,先统计再加减 while(l2*cnt[b[l]]+1 ; l++; } Ans[a[i].id]=ans; } for(re int i=1;i<=m;++i) printf("%lld\n",Ans[i]); }
其次就是对于莫队的修改操作了,带修莫队与原版的差异是多了个时间轴
把修改操作看作删除旧的+增加新的,这种思想很多时候都用的上
也是裸暴力,只不过多关注时间轴上修改都干了什么,这样查询的时候只考虑当前时间之前的操作就好了
分块大小为 (n*t)1/3 复杂度O((n4*t)1/3) ->不会证
模板题 LuoguP1903 数颜色
//窝洛谷突然看不了自己提交的代码了。。。先复制第一篇题解的把,,等哪天我想起来再更(逃
#include#include #include using namespace std; #define fo(a,b,c) for(int a=b;a<=c;a++) #define go(a,b,c) for(int a=b;a>=c;a--) int read(){ int a=0,f=0;char c=getchar(); for(;c<'0'||c>'9';c=getchar())if(c=='-')f=1; for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0'; return f?-a:a; } const int N=10001; int a[N],p[1000001],ans[N],divi; struct nod{int pla,pre,suc;}cg[N]; struct node{int l,r,t,bel;}ls[N]; int cmp(node a,node b){ if(a.l/divi!=b.l/divi)return a.l/divi divi; if(a.r/divi!=b.r/divi)return a.r/divi divi; return a.t<b.t; } int main(){ int n=read(),m=read(),ln=0,tn=0,l=1,r=0,t=0,num=0; fo(i,1,n)a[i]=read(); fo(i,1,m){ scanf("\n"); if(getchar()=='R'){//如果读入修改则记录修改的地点,修改前的数字和修改后的数字 ++tn;cg[tn].pla=read(),cg[tn].suc=read(); cg[tn].pre=a[cg[tn].pla]; a[cg[tn].pla]=cg[tn].suc; } else ls[++ln]=(node){read(),read(),tn,ln}; } divi=ceil(exp((log(n)+log(tn))/3));//分块大小 go(i,tn,1)a[cg[i].pla]=cg[i].pre; sort(ls+1,ls+ln+1,cmp); fo(i,1,m){ while(ls[i].l ; while(ls[i].l>l)num-=!--p[a[l++]];//l移动 while(ls[i].r>r)num+=!p[a[++r]]++; while(ls[i].r //r移动 while(ls[i].t<t){ int pla=cg[t].pla; if(l<=pla&&pla<=r)num-=!--p[a[pla]]; a[pla]=cg[t--].pre; if(l<=pla&&pla<=r)num+=!p[a[pla]]++; }; while(ls[i].t>t){ int pla=cg[++t].pla; if(l<=pla&&pla<=r)num-=!--p[a[pla]]; a[pla]=cg[t].suc; if(l<=pla&&pla<=r)num+=!p[a[pla]]++; };//t移动 ans[ls[i].bel]=num; } fo(i,1,ln)printf("%d\n",ans[i]); return 0; }
二次离线莫队
updataing......