(三维偏序)陌上花开
CDQ分治是一个神奇的东西……说实话我觉得看它的讲义我是真的看不懂。感觉好像分治类的算法都这样,就是讲义讲不明白,得多做题才能慢慢理解。
毕竟因为分治的题主要是不知道怎么实现……得具体题目具体分析。
CDQ分治的精华我觉得是在于,他能通过“修改独立,允许离线”,转化为将序列分为两份,其中保证前一半的操作都早于后一半的询问,从而把动态修改动态查询的问题转换成了先进行一系列修改,最后再查询这么个问题,从而避免使用高级数据结构解决问题,简化代码量,优化时间复杂度。
先来看这道题吧。
这个题相当于修改询问二合一,每次先添加一个点之后查询一下有多少符合情况的点。
首先我们先考虑一下二维的怎么做?排序+树状数组。
那么三位的呢?模仿一下,把第一维排个序,那之后就只剩两维了。二维树状数组
好吧我们显然只能用树状数组维护一维,那我们考虑一下怎么能使第二维也有序?
那我们只能暂且牺牲一下第一维。这里我们就要用到上面的思路了。首先,我们所有的修改对答案的贡献是单独的,都是单独的一个点。因为我们要同时保证三维都小,而在我们把序列劈成两半以后,对于所有后面的询问,前面所有的操作必定x值都要小于他们。也就是转化成了,先进行一通修改,之后再来一通查询这样的问题。
再直白一点说,就是现在x没什么限制作用,就相当于直接转化成了一个二维的问题,只要归并排序第二维或者直接sort第二维(这一点相当于我们处理二维的时候对第一维排序),第三维用树状数组维护统计答案即可。
然后分治区间内部的答案统计递归下去就好了。注意答案是累加的。
这样我们的时间复杂度是O(nlog2n),相对于高级数据结构常数较小,跑的还是比较快的。
至于具体的实现,先对x排序,之后CDQ分治,在一个区间内,对y排序(归并或者sort都可以),之后开始枚举,对于后面每一个操作,把前面所有y小于它的操作都执行一次,之后统计一次答案,之后递归下去就好了。
可能光听人说总是会很懵。看一下代码。
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<queue> #include<cstring> #define rep(i,a,n) for(int i = a;i <= n;i++) #define per(i,n,a) for(int i = n;i >= a;i--) #define enter putchar('\n') #define pr pair<int,int> #define mp make_pair #define fi first #define sc second #define lowbit(x) x & (-x) using namespace std; typedef long long ll; const int M = 200005; const int N = 10000005; int read() { int ans = 0,op = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') op = -1; ch = getchar(); } while(ch >='0' && ch <= '9') { ans *= 10; ans += ch - '0'; ch = getchar(); } return ans * op; } struct tree { int c[M]; void add(int x,int k) {while(x <= M-2) c[x] += k,x += lowbit(x);} int ask(int x) {int p = 0;while(x) p += c[x],x -= lowbit(x);return p;} }T; struct node { int x,y,z,w,ans; bool operator != (const node &g) const { return x != g.x || y != g.y || z != g.z; } bool operator < (const node &g) const { if(x == g.x && y == g.y) return z < g.z; if(x == g.x) return y < g.y; return x < g.x; } }a[M],b[M]; bool cmp(const node &f,const node &g) { if(f.y == g.y) return f.z < g.z; return f.y < g.y; } int n,k,tot,cur,sum[M]; void CDQ(int l,int r) { if(l == r) return; int mid = (l + r) >> 1; CDQ(l,mid),CDQ(mid+1,r);//先分治下去 sort(b+l,b+mid+1,cmp),sort(b+mid+1,b+r+1,cmp);//在里面按第二维排序排序 int i = mid + 1,j = l; while(i <= r) { while(b[j].y <= b[i].y && j <= mid) T.add(b[j].z,b[j].w),j++;//把y在这次操作之前的全部添加 b[i].ans += T.ask(b[i].z),i++;//计算这次答案 } rep(k,l,j-1) T.add(b[k].z,-b[k].w);//把已经添加的操作还原 } int main() { n = read(),k = read(); rep(i,1,n) a[i].x = read(),a[i].y = read(),a[i].z = read(); sort(a+1,a+1+n); rep(i,1,n)//这里是去重,因为有的点是重复的 { cur++; if(a[i] != a[i+1]) b[++tot] = a[i],b[tot].w = cur,cur = 0; } CDQ(1,tot);//分治 rep(i,1,tot) sum[b[i].ans + b[i].w - 1] += b[i].w;//因为这个题要求输出的很奇怪……所以这样计算答案 rep(i,0,n-1) printf("%d\n",sum[i]); return 0; }