四维偏序

我是萌萌的传送门

终于(算是)学会CDQ套CDQ了= =看来我对分治的理解还是不太深,要不然怎么会只会用CDQ压掉一维却不会压掉更高维呢……

题目就是个最简单的四维偏序,为了简化问题把四维都搞成了1~n的排列(废话,出题人可是我自己= =),还把四维LIS换成了四维正序对(该是有多懒……)

四维偏序的话其实压力还是有点大的,首先O(nlog3n)的算法跟暴力差距真的不是特别大,不过经实测除了树套树套树速度和暴力差不多之外剩下的两种做法都是比较快的(经测试n=50000的数据暴力要跑大概6s+,好吧年代久远我忘了具体时间了),所以时限开到2.5s,保证CDQ树套树和CDQ再CDQ都能稳过了。

言归正传。

CDQ树套树的做法很好想,就是对位置分治,这样就可以用扫描线把x压掉,只剩下y和z两维了。由于x已经用离线的扫描线压掉了,必须在线维护y和z,因此可以直接树套树(我写的是树状数组套替罪羊树,由于是随机数据替罪羊树还是很快的)。

  1 #include<cstdio>
  2 #include<cstring>
  3 #include<algorithm>
  4 #define siz(x) ((x)?((x)->size):(0))
  5 #define lowbit(x) ((x)&(-(x)))
  6 using namespace std;
  7 const int maxn=50010;
  8 const double A=0.65;
  9 struct node{//Scapegoat Tree
 10     int data,size;
 11     node *lc,*rc,*prt;
 12     node(int d=0):data(d),size(1),lc(NULL),rc(NULL),prt(NULL){}
 13     inline void refresh(){size=siz(lc)+siz(rc)+1;}
 14 }*root[maxn];
 15 struct B{
 16     int id,a,b,c;
 17     bool operator<(const B &a)const{return this->a<a.a;}
 18 }a[maxn],b[maxn];
 19 void CDQ(int,int);
 20 void add(int,int);
 21 void del(int,int);
 22 int query(int,int);
 23 void insert(int);
 24 void erase(int);
 25 int rank(int);
 26 node *insert(node*);
 27 node *find(int);
 28 node *erase(node*);
 29 node *findmax(node*);
 30 void rebuild(node*);
 31 void zorder(node*);
 32 void removetree(node*);
 33 node *rebuild(int,int);
 34 int n,T,cnt,data[maxn];
 35 long long ans=0ll;
 36 int main(){
 37 #define MINE
 38 #ifdef MINE
 39     freopen("partial_order.in","r",stdin);
 40     freopen("partial_order.out","w",stdout);
 41 #endif
 42     scanf("%d",&n);
 43     for(int i=1;i<=n;i++)a[i].id=i;
 44     for(int i=1;i<=n;i++)scanf("%d",&a[i].a);
 45     for(int i=1;i<=n;i++)scanf("%d",&a[i].b);
 46     for(int i=1;i<=n;i++)scanf("%d",&a[i].c);
 47     CDQ(1,n);
 48     printf("%lld",ans);
 49 #ifndef MINE
 50     printf("\n-------------------------DONE-------------------------\n");
 51     for(;;);
 52 #endif
 53     return 0;
 54 }
 55 void CDQ(int l,int r){
 56     if(l>=r)return;
 57     int mid=(l+r)>>1;
 58     CDQ(l,mid);
 59     CDQ(mid+1,r);
 60     int i=l,j=mid+1,k=l;
 61     while(i<=mid&&j<=r){
 62         if(a[i]<a[j])b[k++]=a[i++];
 63         else b[k++]=a[j++];
 64     }
 65     while(i<=mid)b[k++]=a[i++];
 66     while(j<=r)b[k++]=a[j++];
 67     for(int i=l;i<=r;i++){
 68         a[i]=b[i];
 69         if(a[i].id<=mid)add(a[i].b,a[i].c);
 70         else ans+=query(a[i].b,a[i].c);
 71     }
 72     for(int i=l;i<=r;i++)if(a[i].id<=mid)del(a[i].b,a[i].c);
 73 }
 74 void add(int x,int d){
 75     while(x<=n){
 76         T=x;
 77         insert(d);
 78         x+=lowbit(x);
 79     }
 80 }
 81 void del(int x,int d){
 82     while(x<=n){
 83         T=x;
 84         erase(d);
 85         x+=lowbit(x);
 86     }
 87 }
 88 int query(int x,int d){
 89     int ans=0;
 90     while(x){
 91         T=x;
 92         ans+=rank(d);
 93         x-=lowbit(x);
 94     }
 95     return ans;
 96 }
 97 void insert(int x){
 98     node *rt=insert(new node(x));
 99     if(rt)rebuild(rt);
100 }
101 void erase(int x){
102     node *rt=erase(find(x));
103     if(rt)rebuild(rt);
104 }
105 int rank(int x){
106     node *rt=root[T];
107     int ans=0;
108     while(rt){
109         if(x<=rt->data)rt=rt->lc;
110         else{
111             ans+=siz(rt->lc)+1;
112             rt=rt->rc;
113         }
114     }
115     return ans;
116 }
117 node *insert(node *x){
118     if(!root[T]){
119         root[T]=x;
120         return NULL;
121     }
122     node *rt=root[T];
123     for(;;){
124         if(x->data<rt->data){
125             if(rt->lc)rt=rt->lc;
126             else{
127                 rt->lc=x;
128                 break;
129             }
130         }
131         else{
132             if(rt->rc)rt=rt->rc;
133             else{
134                 rt->rc=x;
135                 break;
136             }
137         }
138     }
139     x->prt=rt;
140     x=NULL;
141     for(;rt;rt=rt->prt){
142         rt->refresh();
143         if(max(siz(rt->lc),siz(rt->rc))>A*rt->size)x=rt;
144     }
145     return x;
146 }
147 node *find(int x){
148     node *rt=root[T];
149     while(rt){
150         if(x==rt->data)return rt;
151         else if(x<rt->data)rt=rt->lc;
152         else rt=rt->rc;
153     }
154     return NULL;
155 }
156 node *erase(node *x){
157     if(x->lc&&x->rc){
158         node *y=findmax(x->lc);
159         x->data=y->data;
160         return erase(y);
161     }
162     if(!x->lc&&!x->rc){
163         if(x->prt){
164             if(x==x->prt->lc)x->prt->lc=NULL;
165             else x->prt->rc=NULL;
166         }
167         else root[T]=NULL;
168     }
169     else if(x->lc&&!x->rc){
170         x->lc->prt=x->prt;
171         if(x->prt){
172             if(x==x->prt->lc)x->prt->lc=x->lc;
173             else x->prt->rc=x->lc;
174         }
175         else root[T]=x->lc;
176     }
177     else if(!x->lc&&x->rc){
178         x->rc->prt=x->prt;
179         if(x->prt){
180             if(x==x->prt->lc)x->prt->lc=x->rc;
181             else x->prt->rc=x->rc;
182         }
183         else root[T]=x->rc;
184     }
185     node *rt=x->prt;
186     delete x;
187     x=NULL;
188     for(;rt;rt=rt->prt){
189         rt->refresh();
190         if(max(siz(rt->lc),siz(rt->rc))>A*rt->size)x=rt;
191     }
192     return x;
193 }
194 node *findmax(node *x){
195     while(x->rc)x=x->rc;
196     return x;
197 }
198 void rebuild(node *rt){
199     cnt=0;
200     zorder(rt);
201     node *x=rebuild(1,cnt);
202     x->prt=rt->prt;
203     if(rt->prt){
204         if(rt==rt->prt->lc)rt->prt->lc=x;
205         else rt->prt->rc=x;
206     }
207     else root[T]=x;
208     removetree(rt);
209 }
210 void removetree(node *x){
211     if(!x)return;
212     removetree(x->lc);
213     removetree(x->rc);
214     delete x;
215 }
216 void zorder(node *x){
217     if(!x)return;
218     zorder(x->lc);
219     data[++cnt]=x->data;
220     zorder(x->rc);
221 }
222 node *rebuild(int l,int r){
223     if(l>r)return NULL;
224     int mid=(l+r)>>1;
225     node *x=new node(data[mid]);
226     x->lc=rebuild(l,mid-1);
227     if(x->lc)x->lc->prt=x;
228     x->rc=rebuild(mid+1,r);
229     if(x->rc)x->rc->prt=x;
230     x->refresh();
231     return x;
232 }

这里还有一份用pb_ds写的代码,你们要学会偷懒哈哈哈。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<ext/pb_ds/assoc_container.hpp>
 5 #include<ext/pb_ds/tree_policy.hpp>
 6 #define lowbit(x) ((x)&(-(x)))
 7 using namespace std;
 8 using namespace __gnu_pbds;
 9 const int maxn=50010;
10 void CDQ(int,int);
11 void add(int,int);
12 void query(int,int);
13 void clear(int);
14 tree<int,null_mapped_type,less<int>,rb_tree_tag,tree_order_statistics_node_update>T[maxn];
15 int n,x[maxn],y[maxn],z[maxn],a[maxn],b[maxn],ans=0;
16 bool cmp(int i,int j){return x[i]<x[j];}
17 int main(){
18 #define MINE
19 #ifdef MINE
20     freopen("partial_order.in","r",stdin);
21     freopen("partial_order.out","w",stdout);
22 #endif
23     scanf("%d",&n);
24     for(int i=1;i<=n;i++)scanf("%d",&x[i]);
25     for(int i=1;i<=n;i++)scanf("%d",&y[i]);
26     for(int i=1;i<=n;i++)scanf("%d",&z[i]);
27     CDQ(1,n);
28     printf("%d\n",ans);
29 #ifndef MINE
30     printf("\n-------------------------DONE-------------------------\n");
31     for(;;);
32 #endif
33     return 0;
34 }
35 void CDQ(int l,int r){
36     if(l>=r)return;
37     for(int i=l;i<=r;i++)a[i]=i;
38     sort(a+l,a+r+1,cmp);
39     int mid=(l+r)>>1;
40     for(int i=l;i<=r;i++){
41         if(a[i]<=mid)add(y[a[i]],z[a[i]]);
42         else query(y[a[i]],z[a[i]]);
43     }
44     for(int i=l;i<=r;i++)if(a[i]<=mid)clear(y[a[i]]);
45     CDQ(l,mid);
46     CDQ(mid+1,r);
47 }
48 void add(int x,int d){
49     while(x<=n){
50         T[x].insert(d);
51         x+=lowbit(x);
52     }
53 }
54 void query(int x,int d){
55     while(x){
56         ans+=T[x].order_of_key(d);
57         x-=lowbit(x);
58     }
59 }
60 void clear(int x){
61     while(x<=n&&!T[x].empty()){
62         T[x].clear();
63         x+=lowbit(x);
64     }
65 }

然而树套树的常数很大,极限数据要跑大概1.1s~1.2s(本地和COGS都是这样),并且树套树并不好写(当然你可以用树状数组套线段树来减少代码量),所以可以再用一次分治压掉一维,剩下最后一维就可以直接树状数组搞掉了。

考虑每次外层的分治,我们都是计算左半部分与右半部分之间的正序对数目,而对x排序之后x已经有序了,剩下无序的y和z可以用树套树维护。既然位置是初始有序的,而x被我们排序后也有序了,我们就可以再对x分治,这样里层的分治就变成了统计位置和x都在左半的元素与位置和x都在右半的元素之间的正序对数量(x在同一半的情况会被里层分治递归解决)。这样处理之后就只剩下y和z了,用扫描线把y压掉,直接用树状数组维护z即可。

为了实现方便,把位置在前一半的元素标记为ins(插入)。a是原序列,b是把x排序之后里层分治用的序列。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=50010;
 6 struct A{
 7     int x,y,z;
 8     bool ins;
 9 }a[maxn],b[maxn];
10 void CDQ1(int,int);
11 void CDQ2(int,int);
12 void add(int,int);
13 int query(int);
14 bool cmpx(const A &x,const A &y){return x.x<y.x;}
15 bool cmpy(const A &x,const A &y){return x.y<y.y;}
16 int n,ans=0,c[maxn]={0};
17 int main(){
18     freopen("partial_order.in","r",stdin);
19     freopen("partial_order.out","w",stdout);
20     scanf("%d",&n);
21     for(int i=1;i<=n;i++)scanf("%d",&a[i].x);
22     for(int i=1;i<=n;i++)scanf("%d",&a[i].y);
23     for(int i=1;i<=n;i++)scanf("%d",&a[i].z);
24     CDQ1(1,n);
25     printf("%d\n",ans);
26     return 0;
27 }
28 void CDQ1(int l,int r){
29     if(l>=r)return;
30     int mid=(l+r)>>1;
31     CDQ1(l,mid);CDQ1(mid+1,r);
32     for(int i=l;i<=mid;i++)a[i].ins=true;
33     for(int i=mid+1;i<=r;i++)a[i].ins=false;
34     sort(a+l,a+r+1,cmpx);//sort x
35     copy(a+l,a+r+1,b+l);
36     CDQ2(l,r);
37 }
38 void CDQ2(int l,int r){
39     if(l>=r)return;
40     int mid=(l+r)>>1;
41     CDQ2(l,mid);CDQ2(mid+1,r);
42     sort(b+l,b+mid+1,cmpy);
43     sort(b+mid+1,b+r+1,cmpy);
44     int i=l,j=mid+1;
45     while(i<=mid&&j<=r){
46         if(b[i].y<b[j].y){
47             if(b[i].ins)add(b[i].z,1);
48             i++;
49         }
50         else{
51             if(!b[j].ins)ans+=query(b[j].z-1);
52             j++;
53         }
54     }
55     while(i<=mid){
56         if(b[i].ins)add(b[i].z,1);
57         i++;
58     }
59     while(j<=r){
60         if(!b[j].ins)ans+=query(b[j].z-1);
61         j++;
62     }
63     for(i=l;i<=mid;i++)if(b[i].ins)add(b[i].z,-1);
64 }
65 void add(int x,int d){
66     while(x<=n){
67         c[x]+=d;
68         x+=x&-x;
69     }
70 }
71 int query(int x){
72     int ans=0;
73     while(x){
74         ans+=c[x];
75         x&=x-1;
76     }
77     return ans;
78 }

还可以进一步优化,两层分治的排序都用归并排序,这样可以减小常数(对复杂度并没有影响)。t是里层的归并排序用的序列。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int maxn=50010;
 6 struct A{
 7     int x,y,z;
 8     bool ins;
 9 }a[maxn],b[maxn],t[maxn];
10 void CDQ1(int,int);
11 void CDQ2(int,int);
12 void add(int,int);
13 int query(int);
14 int n,ans=0,c[maxn]={0};
15 int main(){
16     freopen("partial_order.in","r",stdin);
17     freopen("partial_order.out","w",stdout);
18     scanf("%d",&n);
19     for(int i=1;i<=n;i++)scanf("%d",&a[i].x);
20     for(int i=1;i<=n;i++)scanf("%d",&a[i].y);
21     for(int i=1;i<=n;i++)scanf("%d",&a[i].z);
22     CDQ1(1,n);
23     printf("%d\n",ans);
24     return 0;
25 }
26 void CDQ1(int l,int r){
27     if(l>=r)return;
28     int mid=(l+r)>>1;
29     CDQ1(l,mid);CDQ1(mid+1,r);
30     int i=l,j=mid+1,k=l;
31     while(i<=mid&&j<=r){
32         if(a[i].x<a[j].x){
33             a[i].ins=true;
34             b[k++]=a[i++];
35         }
36         else{
37             a[j].ins=false;
38             b[k++]=a[j++];
39         }
40     }
41     while(i<=mid){
42         a[i].ins=true;
43         b[k++]=a[i++];
44     }
45     while(j<=r){
46         a[j].ins=false;
47         b[k++]=a[j++];
48     }
49     copy(b+l,b+r+1,a+l);
50     CDQ2(l,r);
51 }
52 void CDQ2(int l,int r){
53     if(l>=r)return;
54     int mid=(l+r)>>1;
55     CDQ2(l,mid);CDQ2(mid+1,r);
56     int i=l,j=mid+1,k=l;
57     while(i<=mid&&j<=r){
58         if(b[i].y<b[j].y){
59             if(b[i].ins)add(b[i].z,1);
60             t[k++]=b[i++];
61         }
62         else{
63             if(!b[j].ins)ans+=query(b[j].z-1);
64             t[k++]=b[j++];
65         }
66     }
67     while(i<=mid){
68         if(b[i].ins)add(b[i].z,1);
69         t[k++]=b[i++];
70     }
71     while(j<=r){
72         if(!b[j].ins)ans+=query(b[j].z-1);
73         t[k++]=b[j++];
74     }
75     for(i=l;i<=mid;i++)if(b[i].ins)add(b[i].z,-1);
76     copy(t+l,t+r+1,b+l);
77 }
78 void add(int x,int d){
79     while(x<=n){
80         c[x]+=d;
81         x+=x&-x;
82     }
83 }
84 int query(int x){
85     int ans=0;
86     while(x){
87         ans+=c[x];
88         x&=x-1;
89     }
90     return ans;
91 }

这里是三种写法的对比(pb_ds就别贴了……),不得不说CDQ的威力真是太强大了……

树套树:

CDQ(没有用归并排序优化):

CDQ(归并排序):

这个故事还告诉我们一个道理,写CDQ一定要用归并卡常……

话说我博客真是乱乱乱……我需要整理整理……

时间: 2024-12-22 07:06:43

四维偏序的相关文章

论如何优雅的用bitset来求四维偏序

四维偏序.. 就是给你一个四维集合.再给你一些询问,请你求出a[i].x1<=ask.x1&&a[i].x2<=ask.x2&&a[i].x3<=ask.x3&&a[i].x4<=ask.x4的个数.. 集合大小<=30000 询问个数<=30000 然后怎么做呢?? 其实很简单只要排序+cdq+树状数组套平衡树什么的就行了 qnmd老子不会.. 这时! 神器来了! 那就是bitset! 众所周知,bitset能存一堆二进

COGS2479(四维偏序)

题意:给定一个有n个元素的序列,元素编号为1~n,每个元素有三个属性a,b,c,求序列中满足i<j且ai<aj且bi<bj且ci<cj的数对(i,j)的个数. 分析:cdq分治套cdq分治 对于四维偏序,可以先对第一维排序,然后对第一维分治,按照第二维顺序合并 即cdq(l,r)表示分治第一维,然后把这里面的按照第二维的顺序从小到大排序 然后问题就变成了三维偏序,再套一个cdq2(l,r)去更新答案 不过要注意一点很关键的,那就是按照第二维排序后,第一维已经是乱序了 根据cdq分治

HDU 5126(stars)四维偏序,cdq分治

题意:给两种操作,进行5万次.操作一:加入一个三维序偶(a,b,c)到集合S里:第二种操作,给两个三维序偶(a1,b1,c1)和(a2,b2,c2),问当前S里有多少个序偶(a,b,c)满足a1<=a<=a2, b1<=b<=b2, c1<=c<=c2.题目保证了a1<=a2,b1<=b2,c1<=c2.所有数在[1,1e9]内 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5126 解法:将操作编号也加入到

【教程】CDQ套CDQ——四维偏序问题【转载】

转自 前言 上一篇文章已经介绍了简单的CDQ分治,包括经典的二维偏序和三维偏序问题,还有带修改和查询的二维/三维偏序问题.本文讲介绍多重CDQ分治的嵌套,即多维偏序问题. 四维偏序问题       给定N(N<=20000)个有序四元组(a,b,c,d),求对于每一个四元组(a,b,c,d),有多少个四元组(a2,b2,c2,d2)满足a2<a && b2<b && c2<c && d2<d.        不需要太多思考,就能

COGS 2479. [HZOI 2016]偏序 [CDQ分治套CDQ分治 四维偏序]

传送门 给定一个有n个元素的序列,元素编号为1~n,每个元素有三个属性a,b,c,求序列中满足i<j且ai<aj且bi<bj且ci<cj的数对(i,j)的个数. 对于100%的数据,1<=n<=50000,保证所有的ai.bi.ci分别组成三个1~n的排列. $CDQ$分治套$CDQ$分治也不是很难嘛 对于本题,设四维$a,b,c,d$ $Sort\ at\ a$ $CDQ(l,r)$ $\quad CDQ(l,mid)$ $\quad CDQ(mid+1,r)$ $\

四维偏序(K-D-Tree+rebuild)

其实只是放个代码,会K-D-Tree的同学看了就知道怎么rebuild了,其实也是很简单粗暴的-- 单独再发一次吧,感觉把代码跟之前的一起发不知道啥时候就找不到了-- 1 #include<bits/stdc++.h> 2 #define N 50010 3 #define inf 1000000009 4 #define lson (t1[o].l) 5 #define rson (t1[o].r) 6 using namespace std; 7 typedef long long ll;

COGS 2479 偏序 题解

[题意] 给定一个有n个元素的序列,元素编号为1~n,每个元素有三个属性a,b,c,求序列中满足i<j且ai<aj且bi<bj且ci<cj的数对(i,j)的个数. 对于30%的数据,n<=5000. 对于100%的数据,1<=n<=50000(原题写错了哈哈),保证所有的ai.bi.ci分别组成三个1~n的排列. [解法] 标题已经说了这是偏序,读完题,这就是个四维偏序模板题(位置一维,a,b,c剩下三维). 解法多多,我用的是CDQ树套树(树套树写的树状数组套替

【COGS2479】 HZOI2016—偏序

http://cogs.pro/cogs/problem/problem.php?pid=2479 (题目链接) 题意 四维偏序. Solution CDQ套CDQ. 细节 第二次分治不能直接按照mid分离两类数了. 代码 // cogs2479 #include<algorithm> #include<iostream> #include<cstdlib> #include<cstring> #include<cstdio> #include&

【JZOJ4419】【GDOI2016模拟4.2】hole(四~三维偏序问题)

Problem 给出n次事件,每次事件给出三个非负整数x,y,d.d=0表示在点(x,y)打了一个洞:否则表示询问由(x,y),(x+d,y),(x,y+d)三点围成的三角形中洞的个数. Hint 30%的数据n<=3333 . 另30% 的数据 GFS只会在DSJ打完洞后才开始询问,xi,yi<=333333 . 100%的数据 1<=n<=88888,xi,yi<=3333333 . Solution 算法I:暴力 考虑对于询问(i,j,d),若点(x,y)在该询问的三角