cdq分治学习
经典问题:三维偏序
题:https://www.luogu.org/problem/P3810
一般处理:先按照自己定义的第一维排好序,那么在接下来的俩维判断中,我们就可以消除第一维造成的影响,接着用以前学过的分治排序法来处理第二维,以第二维作为排序对象,对于分治的[l,midd]和[midd+1,r]这俩个区间
对于区间 [m+1, r]中的某个元素x,将区间 [l, m] 的第二维小于x的元素的按第三维的权值加入树状数组,
最后区间 [l, m] 对区间 x 的贡献就是查询树状数组中小于x第三维的个数
可以边进行分治边进行归并排序,树状数组要及时清空
#include<bits/stdc++.h> using namespace std; typedef long long ll; inline int read(){ int sum=0,x=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') x=0; ch=getchar(); } while(ch>='0'&&ch<='9') sum=(sum<<1)+(sum<<3)+(ch^48),ch=getchar(); return x?sum:-sum; } inline void write(ll x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); } #define pb push_back #define lowbit(x) (x&(-x)) const int M=2e5+5; struct node{ int x,y,z,cnt,ans; bool operator < (const node &b)const{ if(x!=b.x) return x<b.x; else if(y!=b.y) return y<b.y; else return z<b.z; } }a[M>>1],temp[M>>1]; int tree[M]; int n,k; int ANS[M]; void add(int i,int x){ while(i<=k){ tree[i]+=x; i+=lowbit(i); } } int query(int i){ int res=0; while(i){ res+=tree[i]; i-=lowbit(i); } return res; } void cdq(int l,int r){ if(l==r){ a[l].ans+=a[l].cnt-1; return ; } int midd=(l+r)>>1; cdq(l,midd); cdq(midd+1,r); int i=l,j=midd+1,op=l; while(j<=r){ while(i<=midd&&a[i].y<=a[j].y){ add(a[i].z,a[i].cnt); temp[op++]=a[i]; i++; } a[j].ans+=query(a[j].z); temp[op++]=a[j]; j++; } for(j=l;j<i;j++) add(a[j].z,-a[j].cnt); while(i<=midd) temp[op++]=a[i],i++; for(i=l;i<=r;i++) a[i]=temp[i]; } int main(){ n=read(),k=read(); for(int i=1;i<=n;i++) a[i].x=read(),a[i].y=read(),a[i].z=read(); sort(a+1,a+1+n); int now=1,cnt=0; for(int i=2;i<=n;i++){ if(a[i-1].x==a[i].x&&a[i-1].y==a[i].y&&a[i-1].z==a[i].z) now++; else{ a[++cnt]=a[i-1]; a[cnt].cnt=now; a[cnt].ans=0; now=1; } } a[++cnt]=a[n]; a[cnt].cnt=now; a[cnt].ans=0; cdq(1,cnt); for(int i=1;i<=cnt;i++) ANS[a[i].ans]+=a[i].cnt; for(int i=0;i<n;i++) printf("%d\n",ANS[i]); return 0; }
题:http://acm.hdu.edu.cn/showproblem.php?pid=5618
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<queue> #define lowbit(i) i&(-i) using namespace std; typedef long long ll; const int M=1e5+5; int tree[M<<1],ans[M]; int n; struct node{ int x,y,z; int id,ans,cnt; bool operator < (const node &b)const{ if(x!=b.x) return x<b.x; else if(y!=b.y) return y<b.y; else return z<b.z; } }a[M],temp[M]; void add(int pos,int val){ while(pos<M) tree[pos]+=val,pos+=lowbit(pos); } int query(int pos){ int res=0; while(pos) res+=tree[pos],pos-=lowbit(pos); return res; } bool cmp1(node p,node q){ return p.x<q.x; } bool cmp2(node p,node q){ return p.id<q.id; } void cdq(int l,int r){ int midd=(l+r)>>1; if(l==r){ return ; } cdq(l,midd); cdq(midd+1,r); int i=l,j=midd+1,m=l; while(j<=r){ while(i<=midd&&a[i].y<=a[j].y){ add(a[i].z,a[i].cnt); temp[m++]=a[i]; i++; } ans[a[j].id]+=query(a[j].z); temp[m++]=a[j]; j++; } for(int p=l;p<i;p++) add(a[p].z,-a[p].cnt); while(i<=midd) temp[m++]=a[i],i++; for(int p=l;p<=r;p++) a[p]=temp[p]; } int main(){ int t; scanf("%d",&t); while(t--){ scanf("%d",&n); for(int i=1;i<=n;i++) ans[i]=0; for(int i=1;i<=n;i++) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z),a[i].cnt=1,a[i].id=i; sort(a+1,a+1+n); int now=0; for(int i=n-1;i>=1;i--){ if(a[i].x==a[i+1].x&&a[i].y==a[i+1].y&&a[i].z==a[i+1].z) now++; else now=0; ans[a[i].id]+=now; } cdq(1,n); for(int i=1;i<=n;i++) printf("%d\n",ans[i]); } return 0; }
例:https://codeforces.com/problemset/problem/669/E
题意:没给定的三个数分别表示操作,时刻(在t时刻进行这项操作),val
增加或减去这个val的个数一次,查询val的个数
分析:一开始误以为是按照时间来排序然后用单点更新,单点查询操作就行了,其实不然,他是按照给定顺序进行操作的,时间可以看出多一个维度(这可以通过下面的样例模拟一下)
/*
10 1 1 1000000000 1 4 1000000000 2 2 1000000000 1 5 1000000000 1 8 1000000000 2 15 1000000000 3 3 1000000000 3 10 1000000000 3 6 1000000000 3 7 1000000000
answer output
0 3 2 2
*/
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define pb push_back #define lowbit(x) (x&(-x)) #define lson root<<1,l,midd #define rson root<<1|1,midd+1,r inline int read(){ int sum=0,x=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') x=0; ch=getchar(); } while(ch>='0'&&ch<='9') sum=(sum<<1)+(sum<<3)+(ch^48),ch=getchar(); return x?sum:-sum; } inline void write(ll x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); } const int M=1e5+5; int ans[M]; struct node{ int op,ti,val,cnt,id; bool operator< (const node &b)const{ return ti<b.ti; } }a[M],temp[M]; int lisan[M],m; map<ll,ll>countt; void cdq(int l,int r){ if(l==r) return ; int midd=(l+r)>>1; cdq(l,midd); cdq(midd+1,r); int i=l,j=midd+1,mm=l; while(j<=r){ while(i<=midd&&a[i].ti<=a[j].ti){ if(a[i].op!=3){ countt[a[i].val]+=a[i].cnt; } temp[mm++]=a[i]; i++; } if(a[j].op==3) ans[a[j].id]+=countt[a[j].val]; temp[mm++]=a[j]; j++; } for(j=l;j<i;j++) if(a[j].op!=3) countt[a[j].val]-=a[j].cnt; while(i<=midd) temp[mm++]=a[i],i++; for(i=l;i<=r;i++) a[i]=temp[i]; return ; } int main(){ int n=read(); int num=0,tot=0; for(int i=1;i<=n;i++){ a[i].op=read(); a[i].ti=read(); a[i].val=read(); if(a[i].op==3) a[i].id=++tot,a[i].cnt=0; else if(a[i].op==1) a[i].cnt=1; else a[i].cnt=-1; lisan[++num]=a[i].val; } sort(lisan+1,lisan+1+num); m=unique(lisan+1,lisan+1+num)-lisan-1; for(int i=1;i<=n;i++) a[i].val=lower_bound(lisan+1,lisan+1+num,a[i].val)-lisan; cdq(1,n); for(int i=1;i<=tot;i++) printf("%d\n",ans[i]); return 0; }
题:https://www.luogu.org/problem/P3157
P3157 [CQOI2011]动态逆序对
删除一个数他减少的逆序数个数等于他前面比它大的和他后面比它小的之和,为了不造成删了当前这个点对后面删的点的影响,所以要从后面开始删,这里用这个删的次序作为排序的一维
所以当前的三维:位置,这个位置的值,这个位置删的次序
三维偏序用cdq分治
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define lowbit(pos) pos&(-pos) int n,m; const int M=1e5+5; ll ans[M]; int pos[M]; struct NODE{ int pos,id,v,t; bool operator <(const NODE &b){ return t<b.t; } }a[M],temp[M]; struct TREE{ int tree[M]; void init(){ for(int i=0;i<=n;i++) tree[i]=0; } void add(int pos,int c){ while(pos<=n) tree[pos]+=c,pos+=lowbit(pos); } int sum(int pos){ int res=0; while(pos){ res+=tree[pos]; pos-=lowbit(pos); } return res; } }T; void cdq1(int l,int r){ int midd=(l+r)>>1; if(l==r) return ; cdq1(l,midd); cdq1(midd+1,r); int p=l,q=midd+1,tp=l; while(q<=r){ while(p<=midd&&a[p].pos<a[q].pos){ T.add(a[p].v,1); temp[tp++]=a[p]; p++; } ans[a[q].id]+=T.sum(n)-T.sum(a[q].v); temp[tp++]=a[q]; q++; } for(int i=l;i<p;i++) T.add(a[i].v,-1); while(p<=midd) temp[tp++]=a[p],p++; for(int i=l;i<=r;i++) a[i]=temp[i]; } void cdq2(int l,int r){ if(l==r) return ; int midd=(l+r)>>1; cdq2(l,midd); cdq2(midd+1,r); int p=l,q=midd+1,tp=l; while(q<=r){ while(p<=midd&&a[p].pos>a[q].pos){ T.add(a[p].v,1); temp[tp++]=a[p]; p++; } ans[a[q].id]+=T.sum(a[q].v-1); temp[tp++]=a[q]; q++; } for(int i=l;i<p;i++) T.add(a[i].v,-1); while(p<=midd) temp[tp++]=a[p],p++; for(int i=l;i<=r;i++) a[i]=temp[i]; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ scanf("%d",&a[i].v); a[i].pos=i; a[i].t=0; a[i].id=0; pos[a[i].v]=i; } ll res=0ll; for(int i=1;i<=n;i++){ res+=T.sum(n)-T.sum(a[i].v); T.add(a[i].v,1); } T.init(); for(int x,i=1;i<=m;i++){ scanf("%d",&x); a[pos[x]].t=m-i+1; a[pos[x]].id=i; } ///删除一个数对逆序数的影响为减去这个数前面比他大的和后面比他小之和 //先算前面比他大的 sort(a+1,a+1+n); cdq1(1,n); //算后面比他小的 sort(a+1,a+1+n); cdq2(1,n); for(int i=1;i<=m;i++){ printf("%lld\n",res); res-=ans[i]; } return 0; }