NOIP前这么一点点时间,我还是花了一晚上简单学了一下CDQ分治解决偏序问题。
不过暂时来讲,还是只会比较裸一点的,它的精髓还是没有完全掌握。以后有时间会完善吧。。
但是就算只会很裸很裸的,也还是想讲讲。
首先二维偏序板子:
a,b两个序列,长度都为n,求有多少个数对(i,j)满足:a[i]<a[j],b[i]<b[j]。(值得注意的是这里的i,j是没规定大小的)
可以看到,这种两个序列被要求满足一定大小关系的问题就是二维偏序问题。。
那么的话,怎么求呢??
可以这么考虑,如果我们只有一维是不是直接求个正序对就行了。
现在有两维的话,我们考虑如果有一个维度已经有序的话,那么就只要考虑第二维满足条件的就好了。
所以就是按第一维排序,第二维正序对就好了,至于归并还是树状数组就随意了。。
代码的话相信强大的您不需要吧 ~O(∩_∩)O~
那么三维偏序呢??
可以这么看吧,其实就是在二维基础上,多一个序列,多一个限制条件而已。
那么的话我们还是类似的,第一维排序解决。
还剩下两维怎么搞??
没问题。。我们直接归并排序刚第二维。
考虑合并时,右区间每个数都满足第二维大于左区间,同时第一维也是满足条件,那么
我们只要考虑对于每一个右区间里的数,左区间里有多少满足第三维条件的就好了。。
这样的话我们同样可以建立权值树状数组,维护的是左区间第三维的一个信息,
具体一点就是每从左边来一个数,就在对应位置上+1,右边来一个数,就直接查询比他小的有多少就好了。
至此,三维偏序基本可以很好的解决了。
时间复杂度O(nlog2),空间复杂度O(值域)。
但是,如果是值域很大呢?你也许还可以离散化一下。
那如果是n维偏序呢,莫非树套树套树套树。。。??也许对dalao仅仅是复杂点,对我的话就直接GG了。。
那么这就可以用到来CDQ分治有效代替高级复杂的数据结构了。
什么是CDQ分治?其实归并排序求逆序对就有CDQ分治的思想。
我的认为大概就是,分别处理子问题后,考虑前一个子问题对后一个子问题造成的影响或产生的贡献,从而得到答案。
就拿三位偏序为例吧,我们用归并套归并,也就是CDQ套CDQ来解决。
同样的,我们只要考虑对于每一个右区间里的数,左区间里有多少满足第三维条件的就好了。
这其实应该是CDQ分治最擅长解决的问题。
我们考虑把每一个数是从左还是右来(第二维排序下),然后在先合并后(保证第二维在之后的归并中不受影响),继续第三维的归并。
那么考虑对于第三维的归并,我们已知一些什么?
最重要的就是我们记录了他在第二维意义下来自左边还是右边。。
那么我们对于每一个第三维排序下,
每从左边来一个数,看他第二维排序是否来自左边,是的话cnt++,
说明它可以对后面的 每一个(第三维排序下)来自右区间,(第二维排序下)来自右区间的数造成1的贡献。
而对于来自右区间的数只要满足上述条件,Ans+=cnt就好了。
然后贴下三维偏序模板(陌上花开)代码吧
#include <bits/stdc++.h> using namespace std; inline int gi () { int x=0, w=0; char ch=0; while (! (ch>='0' && ch<='9') ) { if (ch=='-') w=1; ch=getchar (); } while (ch>='0' && ch<='9') { x= (x<<3) + (x<<1) + (ch^48); ch=getchar (); } return w?-x:x; } const int N=1e5+10; int n,k,d[N],Ans[N]; struct Seq { bool Tag; int *Ans; int a, b, c; bool operator == (const Seq &s) const { return a==s.a && b==s.b && c==s.c; } }seq[N],sB[N],sC[N]; inline bool CMP (Seq x, Seq y) { return x.a<y.a || (x.a==y.a && x.b<y.b) || (x.a==y.a && x.b==y.b && x.c<y.c); } void Merge2 (int l, int r) { if (l==r) return; int Mid= (l+r) >>1; Merge2 (l, Mid); Merge2 (Mid+1, r); for (int i=l, j=Mid+1, id=l, cnt=0;id<=r;++id) { if ( (j>r || sB[i].c<=sB[j].c) && i<=Mid) sC[id]=sB[i++], cnt+=sC[id].Tag; else { sC[id]=sB[j++]; if (!sC[id].Tag) *sC[id].Ans+=cnt; } } for (int i=l;i<=r;++i) sB[i]=sC[i]; } void Merge1 (int l, int r) { if (l==r) return; int Mid= (l+r) >>1; Merge1 (l, Mid); Merge1 (Mid+1, r); for (int i=l, j=Mid+1, id=l;id<=r;++id) { if ( (j>r || seq[i].b<=seq[j].b ) && i<=Mid) sB[id]=seq[i++], sB[id].Tag=1; else sB[id]=seq[j++], sB[id].Tag=0; } for (int i=l;i<=r;++i) seq[i]=sB[i]; Merge2 (l, r); } int main () { n=gi (), k=gi (); for (int i=1;i<=n;++i) { seq[i].a=gi (), seq[i].b=gi (), seq[i].c=gi (); seq[i].Ans=&Ans[i], Ans[i]=0; } sort (seq+1, seq+n+1, CMP); for (int i=n-1;i;--i) if (seq[i]==seq[i+1]) *seq[i].Ans=*seq[i+1].Ans+1; Merge1 (1, n); for (int i=1;i<=n;++i) d[Ans[i]]++; for (int i=0;i<n;++i) printf ("%d ", d[i]); return 0; }
其实可以发现这样的话n维偏序也只要多打几个归并,免去了高维下复杂的数据结构,还是挺好用的。
缺点可能就是比树状数组什么的常数大些吧 还有就是很多拓展问题用CDQ解决只能离线。。
大概我就搞了这么点东西吧,东西少,话却说了蛮多。另外的话感谢一下尻哥的友情帮助啦。