[CQOI2011]动态逆序对 CDQ分治
洛谷上有2道相同的题目(基本是完全相同的,输入输出格式略有不同)
CDQ分治
首先由于删除是很不好处理的,所以我们把删除改为插入,然后输出的时候倒着输出即可
首先这是一个类似于3维偏序的问题:
那么为了理解我的算法,这里先放出我对三维偏序的理解。
三维偏序实质上是在所有二维偏序中所有符合条件的数对中进一步找出符合另一个限制条件的数对。
这里找所有二维偏序中所有符合条件的数对可以用归并排序(就是相当于归并排序求逆序对)
那么树状数组求逆序对是个怎么回事呢?
相当于单独统计每个数对答案的贡献,由于树状数组的特殊性(前缀和)以及不重不漏的原则,我们只统计在一个数前面出现的,即数对(a,b)中,这个数对对答案的贡献只统计在b中(下标靠后的那个)
那么知道这些后我们就可以开始解题了!
将删除转为插入后,问题就可以转化为一个类似于三维偏序的问题:
对于每个数,我们只统计在它之前被插入的数对它造成的贡献(没有被删除的数默认为第一个插入,不然树状数组统计不了)
举个栗子:
我们观察样例: 如果是删除的话,变化过程是这样的(这里为了简化以方便理解,我们暂时不看最后一个删除)
5 4 2 6 3 1
5 2 6 3 1
2 6 3 1
如果是插入的话,变化过程自然就是反过来
2 6 3 1 ---> ans=4
5 2 6 3 1 ---> ans=7
5 4 2 6 3 1 ---> ans=11
此处注意:由于将删除变为了插入,因此最先删除的反而是最后插入的,下面的编号就是插入顺序
数字 | 5 | 4 | 2 | 6 | 3 | 1 |
---|---|---|---|---|---|---|
编号(t) | 2 | 3 | 1 | 1 | 1 | 1 |
统计到的贡献 | 0 | 1 | 0 | 0 | 1 | 3 |
这里由于我们是分时间输出,而不是分数字输出,因此我们将每个数字的贡献放入它所对应的编号内
所以我们的ans数组:
ans | 4 | 0 | 1 | |||
---|---|---|---|---|---|---|
下标 | 1 | 2 | 3 |
又由于编号和输出顺序是相反的,并且观察插入的特点,逆序对在插入某个数后不可能会变少,只会不变or上升,因此我们的答案应该是对于ans数组的前缀和,并且要倒着输出这个前缀和
那么我们将会输出 4 4 5
嗯?好像有哪里不对?
为什么呢?
我们可以发现,我们其实是单独统计了每个数在插入后的贡献,
但是我们就像传统的树状数组求逆序对一样求在一个数前面的数对是不行的
why?
树状数组求逆序对对于每个数只求在它前面的数对之所以可行,就是因为在它后面的数对会被在它后面的那个数所统计到
但是在这里,对于一个被删除的数来说,它可以统计到别的数,别的数可统计不到它啊
那这么办呢?
也许我们可以考虑%&%……##@#¥等一大堆妙妙的算法,
但是!归并排序并不慢啊!
所以我们考虑暴力一点的解法,
既然只统计前面的,会漏掉后面的,
那么我们就统计两次啊!一次统计前面的,一次统计后面的,加在一起不就好了
但是我们会观察到,只有被删除的数才会漏掉应该被统计到的贡献,而没有被删除的数是不会的,所以如果我们统计两次,那么被删除的数会统计到正确答案,但是没有被删除的数会统计到双倍贡献,但是这并没有什么影响,在统计完后把没有被删除的数的贡献和/2即可
第一次归并排序是要筛选出在一个数前面的并且比它大的数(符合基础逆序对)
第二次归并排序是要筛选出在一个数后面的并且比它小的数(符合基础逆序对)
以下是代码(注意两次归并排序由于要筛选出的东西是不同的(大小关系和下标关系头不同,因此在细节上略有差别):
--- 2018.10.04 优化了代码格式 ---
---2018.11.04 发现第二份代码格式没改,,,又改了一下---
下面为2题的代码
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define AC 40100 5 #define lowbit(x) (x&(-x)) 6 #define LL long long 7 #define D printf("line in %d\n",__LINE__); 8 int n, m, tot = 1; 9 int c[AC];//这。。。不需要离散化? 10 LL ans[AC];//还是比较妙的?改删除为插入,这样就是三维偏序问题,然后统计答案的时候, 11 //计算时,一个数的贡献统计入时间靠后的那个数的ans[时间]里 12 //每次都加上前一个的时间贡献(前缀和),这样就可以分别统计到删除前的和删除后的ans。 13 struct node{ 14 int t, num; 15 }s[AC], tmp[AC], s1[AC]; 16 17 inline int read() 18 { 19 int x = 0;char c = getchar(); 20 while(c > '9' || c < '0') c = getchar(); 21 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 22 return x; 23 } 24 25 void pre() 26 { 27 int a; 28 n = read(), m = read(); 29 tot = m + 1;//error!!!因为删除变成了插入,所以第一个删除的反而是最后一个插入的 30 for(R i = 1; i <= n; i++) 31 s[i].num = read(),s[i].t = 1,s1[i].num = s[i].num,s1[i].t = 1; 32 for(R i = 1; i <= m; i++) a = read(), s[a].t = tot, s1[a].t = tot--; 33 m ++; 34 } 35 36 inline void add(int x){ 37 for(R i = x; i <= m; i += lowbit(i)) c[i]++;//error!!!这里的m可能超过n,所以只添加到n是不够的,可能会导致查询不到 38 } 39 40 inline void cut(int x){ 41 for(R i = x; i <= m;i += lowbit(i)) c[i]--; 42 } 43 44 inline void search(node x){ 45 for(R i = x.t; i;i -= lowbit(i)) ans[x.t] += c[i];//直接累加到对应的时间里 46 } 47 48 void merge_sort(int l, int r)//归并,按num排序,用树状数组统计合法的t 49 { 50 int mid = (l + r) >> 1, t = 0; 51 if(l == r) return ; 52 if(l < r) merge_sort(l, mid), merge_sort(mid + 1, r); 53 int i = l, j = mid + 1; 54 while(i <= mid && j <= r) 55 {//因为num已经符合,所以只要找在它前面的就好了 56 if(s[i].num > s[j].num) add(s[i].t), tmp[++t] = s[i++]; 57 else search(s[j]), tmp[++t] = s[j++]; 58 } 59 while(i <= mid) add(s[i].t), tmp[++t] = s[i++]; 60 while(j <= r) search(s[j++]); 61 for(R i = l; i <= mid; i++) cut(s[i].t); 62 for(R i = 1; i <= t; i++) s[l + i - 1] = tmp[i]; 63 } 64 65 void merge_sort_two(int l, int r)//因为上面会少统计,所以这里再统计一次 66 { 67 int mid = (l + r) >> 1, t = 0; 68 if(l == r) return ; 69 if(l < r) merge_sort_two(l, mid), merge_sort_two(mid + 1, r); 70 int i = l, j = mid + 1; 71 while(i <= mid && j <= r) 72 {//因为num已经符合,所以只要找在它前面的就好了 73 if(s1[j].num < s1[i].num) add(s1[j].t), tmp[++ t] = s1[j ++]; 74 else search(s1[i]), tmp[++ t] = s1[i ++]; 75 } 76 while(j <= r) add(s1[j].t), tmp[++ t] = s1[j ++]; 77 while(i <= mid) search(s1[i]), tmp[++ t] = s1[i ++]; 78 for(R i = mid + 1; i <= r; i ++) cut(s1[i].t); 79 for(R i = 1; i <= t; i ++) s1[l + i - 1] = tmp[i]; 80 } 81 82 void work() 83 { 84 ans[1] >>= 1; 85 for(R i = 1; i <= m; i ++) ans[i] += ans[i - 1]; 86 for(R i = m; i > 1; i --) printf("%lld ", ans[i]); 87 printf("%lld", ans[1]); 88 } 89 int main() 90 { 91 freopen("in.in", "r", stdin); 92 pre(); 93 merge_sort(1, n); 94 merge_sort_two(1, n); 95 work(); 96 fclose(stdin); 97 return 0; 98 }
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define AC 100100 5 #define lowbit(x) (x&(-x)) 6 #define LL long long 7 #define D printf("line in %d\n",__LINE__); 8 int n, m, tot = 1; 9 int c[AC], w[AC];//这。。。不需要离散化? 10 LL ans[AC];//还是比较妙的?改删除为插入,这样就是三维偏序问题,然后统计答案的时候, 11 //计算时,一个数的贡献统计入时间靠后的那个数的ans[时间]里 12 //每次都加上前一个的时间贡献(前缀和),这样就可以分别统计到删除前的和删除后的ans。 13 struct node{ 14 int t, num; 15 }s[AC], tmp[AC], s1[AC]; 16 17 inline int read() 18 { 19 int x = 0;char c = getchar(); 20 while(c > '9' || c < '0') c = getchar(); 21 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 22 return x; 23 } 24 25 void pre() 26 { 27 int a; 28 n = read(), m = read(); 29 tot = m + 1;//error!!!因为删除变成了插入,所以第一个删除的反而是最后一个插入的 30 for(R i = 1; i <= n; i ++) 31 { 32 s[i].num = read(), s[i].t = 1; 33 s1[i].num = s[i].num, s1[i].t = 1, w[s[i].num] = i;//error!!!这里是删除元素,给的是元素,不是下标 34 } 35 for(R i = 1; i <= m; i ++) 36 a = read(), s[w[a]].t = tot, s1[w[a]].t = tot--; 37 m ++; 38 } 39 40 inline void add(int x){ 41 for(R i = x; i <= m; i += lowbit(i)) c[i] ++;//error!!!这里的m可能超过n,所以只添加到n是不够的,可能会导致查询不到 42 } 43 44 inline void cut(int x){ 45 for(R i = x; i <= m; i += lowbit(i)) c[i] --; 46 } 47 48 inline void search(node x){ 49 for(R i = x.t; i ;i -= lowbit(i)) ans[x.t] += c[i];//直接累加到对应的时间里 50 } 51 52 void merge_sort(int l, int r)//归并,按num排序,用树状数组统计合法的t 53 { 54 int mid = (l + r) >> 1, t = 0; 55 if(l == r) return ; 56 if(l < r) merge_sort(l, mid), merge_sort(mid + 1, r); 57 int i = l, j = mid + 1; 58 while(i <= mid && j <= r) 59 { 60 if(s[i].num > s[j].num) add(s[i].t), tmp[++ t] = s[i ++]; 61 else search(s[j]), tmp[++t]=s[j++]; 62 } 63 while(i <= mid) add(s[i].t), tmp[++t]=s[i++]; 64 while(j <= r) search(s[j++]); 65 for(R i = l; i <= mid; i ++) cut(s[i].t); 66 for(R i = 1; i <= t; i ++) s[l + i - 1] = tmp[i]; 67 } 68 69 void merge_sort_two(int l, int r)//因为上面会少统计,所以这里再统计一次 70 { 71 int mid=(l + r) >> 1, t = 0; 72 if(l == r) return ; 73 if(l < r) merge_sort_two(l,mid), merge_sort_two(mid+1,r); 74 int i = l, j = mid + 1; 75 while(i <= mid && j <= r) 76 { 77 if(s1[j].num < s1[i].num) add(s1[j].t), tmp[++ t] = s1[j ++]; 78 else search(s1[i]), tmp[++ t] = s1[i ++]; 79 } 80 while(j <= r) add(s1[j].t), tmp[++ t] = s1[j ++]; 81 while(i <= mid) search(s1[i]), tmp[++t]=s1[i++]; 82 for(R i = mid + 1; i <= r; i ++) cut(s1[i].t); 83 for(R i = 1; i <= t; i ++) s1[l + i - 1] = tmp[i]; 84 } 85 86 void work() 87 { 88 ans[1] >>= 1; 89 for(R i = 1; i <= m; i ++) ans[i] += ans[i - 1]; 90 for(R i = m; i > 1; i --) printf("%lld\n", ans[i]); 91 } 92 int main() 93 { 94 // freopen("in.in","r",stdin); 95 pre(); 96 merge_sort(1,n), merge_sort_two(1,n); 97 work(); 98 // fclose(stdin); 99 return 0; 100 }