CDQ分治
CDQ分治
它是一种非常巧妙地方法。
用分治的思想处理三维偏序一类的问题:
处理的问题如下:模板
有 $ n $ 个元素,第 $ i $ 个元素有 $ a_i,b_i,c_i $ 三个属性,设 $ f(i) $ 表示满足 $ a_j \leq a_i $ 且 $ b_j \leq b_i $ 且 $ c_j \leq c_i $ 且 $ j \ne i $ 的 \(j\) 的数量。
对于 $ d \in [0, n) $ ,求 $ f(i) = d $ 的数量。
也就是对于每一个三元组 \(i\) 看有多少个完全小于他的三元组 \(j\)。
数据范围 \(n \le 2\times 10^5\)。
显然暴力会不过去。
只能考虑一些 \(n \log n\) 或 \(n \log^2 n\) 的做法。
首先,先把第一维排序了。
这样,只用考虑 \(b_i\) 与 \(c_i\) 了。
那我们考虑如何处理 \(b_i\)。
类似归并的方法,分而治之,分治。就是对于 \([l,r]\) 的子区间我们先处理 \([l,mid]\) 和 \([mid+1,r]\) 两个区间,然后将其 \(O(r-l)\) 的时间复杂度合并。很简单的策略,分别在两个子区间内放两个指针,那边小就加入那边,因为如果 \(A_i\) 他是比 \(B_j\) 小的,那么 \(A\) 目前也排序了,所以也是 \(A\) 中最小的,也就是 \(A\) 和 \(B\) 合并后最小的,因此直接判断然后去加即可。
这样,\(b_i\) 也处理好了,我们就可以用树状数组维护一下 \(c_i\) 的数量,然后就可以求出此时比 \(c_i\) 小的 \(c_j\) 有多少个,由于在线处理,所以说,之前出现的 \(a_i\) 和 \(b_i\) 都是应该符合小于现在的这个三元组的。
于是,在线(边跑边记录)处理完之后,再依次输出答案。
int n,k,m;
struct node{int a,b,c;};
struct data{node a;int cnt,ans;};
node a[N];
struct data b[N];
int t[N<<1];
void add(int x,int val){while(x<=k)t[x]+=val,x+=(x&-x);}
int query(int x){int res=0;while(x)res+=t[x],x-=(x&-x);return res;}
bool node_cmp(node a,node b){
if(a.a != b.a) return a.a<b.a;
if(a.b != b.b) return a.b<b.b;
return a.c<b.c;
}bool data_cmp(struct data a,struct data b){
if(a.a.b != b.a.b) return a.a.b<b.a.b;
return a.a.c<b.a.c;
}void cdq(int l,int r){
if(l==r)return ;
int mid=(l+r)>>1,x=l;
cdq(l,mid),cdq(mid+1,r);
sort(b+l,b+1+mid,data_cmp),sort(b+1+mid,b+1+r,data_cmp);
For(i,mid+1,r){
while(x<=mid&&b[x].a.b<=b[i].a.b) add(b[x].a.c,b[x].cnt),x++;
b[i].ans+=query(b[i].a.c);
}For(i,l,x-1){
add(b[i].a.c,-b[i].cnt);//清空还原操作
}
}int ans[N];
void solve(){
cin>>n>>k;
For(i,1,n)cin>>a[i].a>>a[i].b>>a[i].c;
sort(a+1,a+1+n,node_cmp);
For(i,1,n){
if(a[i].a!=a[i-1].a||a[i].b!=a[i-1].b||a[i].c!=a[i-1].c) b[++m].a=a[i];
b[m].cnt++;
}
cdq(1,m);
For(i,1,m)ans[b[i].ans+b[i].cnt]+=b[i].cnt;
For(i,1,n)cout<<ans[i]<<endl;
}
本文来自博客园,作者:gsczl71,转载请注明原文链接:https://www.cnblogs.com/gsczl71/p/18156173
gsczl71 AK IOI!RP = INF 2024年拿下七级勾!