首先说明,CDQ分治与整体二分都是离线算法
CDQ分治:
流程:
1.我们要解决一系列问题,这些问题一般包含修改和查询操作,可以把这些问题排成一个序列,用一个区间[L,R]表示。
2.分。递归处理左边区间[L,M]和右边区间[M+1,R]的问题。
3.治。合并两个子问题,同时考虑到[L,M]内的修改对[M+1,R]内的查询产生的影响。即,用左边的子问题帮助解决右边的子问题。
经典问题:三维偏序
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=100010;
const int K=200010;
struct node{int x,y,z,ans,w;
}a[N],b[N];
int tot,cnt[K],k,n;
bool cmpx(node a,node b){if(a.x==b.x){if(a.y==b.y) return a.z<b.z;return a.y<b.y;}return a.x<b.x;
}
bool cmpy(node a,node b){if(a.y==b.y) return a.z<b.z;return a.y<b.y;
}
int c[K];
int lowbit(int x){return x&(-x);}
void add(int i,int x){for(;i<=k;i+=lowbit(i))c[i]+=x;
}
int sum(int i){int res=0;for(;i>0;i-=lowbit(i))res+=c[i];return res;
}
void cdq(int l,int r){if(l==r) return;int mid=(l+r)>>1;cdq(l,mid);cdq(mid+1,r);sort(a+l,a+mid+1,cmpy);sort(a+mid+1,a+r+1,cmpy);int i=mid+1,j=l;for(;i<=r;i++){while(a[j].y<=a[i].y&&j<=mid){add(a[j].z,a[j].w);j++;}a[i].ans+=sum(a[i].z);}for(i=l;i<j;i++)add(a[i].z,-a[i].w);
}
int main(){scanf("%d%d",&n,&k);for(int i=1;i<=n;i++){scanf("%d%d%d",&b[i].x,&b[i].y,&b[i].z);}sort(b+1,b+n+1,cmpx);int c=0;for(int i=1;i<=n;i++){c++;if(b[i].x!=b[i+1].x||b[i].y!=b[i+1].y||b[i].z!=b[i+1].z){a[++tot]=b[i];a[tot].w=c;c=0;}}cdq(1,tot);for(int i=1;i<=tot;i++)cnt[a[i].ans+a[i].w-1]+=a[i].w;for(int i=0;i<n;i++) printf("%d\n",cnt[i]);return 0;
}
四维偏序
整体二分:
产生原因:
对于单个查询而言,我们可以采用预处理+二分答案的方法解决,
但往往我们要回答的是一系列的查询,所以我们将所有操作(包括修改和查询)一起二分,进行分治,即整体二分。
模板:Meteors
#include<iostream>
#include<cstdio>
using namespace std;
#define ll long long
#define N 300010
inline int read() {char c=getchar(); int x = 0, f = 1;while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();}while(c>='0'&&c<='9') x=x*10+c-'0', c=getchar();return x*f;
}
struct Node{int head,id;ll need;
}node[N],node_[N<<1];
int n,m,k,L[N],R[N];
ll A[N];
int ans[N],nxt[N],to[N],idx;
ll tmp[N<<1];
void add_edge(int a,int b){nxt[++idx]=node[a].head;to[idx]=b;node[a].head=idx;
}
void add(int x,ll y){while(x<=2*m){tmp[x]+=y;x+=x&-x;}
}
ll sum(int x){ll sum=0;while(x){sum+=tmp[x];x-=x&-x; } return sum;
}
void solve(int l,int r,int x,int y){if(x>y) return;if(l==r){for(int i=x;i<=y;i++) ans[node[i].id]=l;return;}int mid=(l+r)>>1,tl=0,tr=n;for(int i=l;i<=mid;i++){add(L[i],A[i]);add(R[i]+1,-A[i]);}for(int i=x;i<=y;i++){ll tmp1=0;for(int j=node[i].head;j&&tmp1<=node[i].need;j=nxt[j])tmp1+=sum(to[j]+m)+sum(to[j]);if(tmp1>=node[i].need) node_[++tl]=node[i];else node_[++tr]=node[i],node_[tr].need-=tmp1;}for(int i=l;i<=mid;i++){add(L[i],-A[i]),add(R[i]+1,A[i]);} for(int i=1;i<=tl;i++) node[x+i-1]=node_[i];for(int i=n+1;i<=tr;i++) node[x+tl+i-n-1]=node_[i];solve(l,mid,x,x+tl-1);solve(mid+1,r,y-tr+n+1,y);
}
int main(){scanf("%d%d",&n,&m);for(int i=1,a;i<=m;i++){a=read();add_edge(a,i);} for(int i=1;i<=n;i++){node[i].need=read();node[i].id=i;} scanf("%d",&k);for(int i=1;i<=k;i++){scanf("%d%d%lld",&L[i],&R[i],&A[i]);} for(int i=1;i<=k;i++)if(R[i]<L[i]) R[i]+=m; solve(1,k+1,1,n);for(int i=1;i<=n;i++)ans[i]==k+1?printf("NIE\n"):printf("%d\n",ans[i]);
}
异同:
同:
1.都是按时间进行分治
2.代码很像(不完全一样,这在异中会讲到)
3.复杂度都是O(f(n)logn)
异:
1.整体二分有二分答案操作
2.适用范围不同