CDQ分治属于比较特殊的一类分治,许多问题转化为这类分治的时候,时空方面都会有很大节省,而且写起来没有这么麻烦。
这类分治的特殊性在于分治的左右两部分的合并,作用两部分在合并的时候作用是不同的,比如,通过左半部分的影响来更新右半部分,所以分治开始前都要按照某一个关键字排序,然后利用这个顺序,考虑一个区间[l, r]的两部分间的影响。感觉说的太多,还是不如具体题目分析,而且题目也不尽相同,记住几句话是没什么用的。
练习地址:
http://vjudge.net/contest/view.action?cid=55322#overview
Problem A HYSBZ 3110 K大数查询
这个是一个很经典的树套树题目,看别人博客发现CDQ分治能够很好处理。
题意:有n个位置编号1~n,m个操作,每个操作两种类型:1 a b c 表示将第a~b个位置之间的每个位置插入一个数c;2 a b c 查询第a~b个位置之间的所有数中,第c大的数。
范围:
N,M<=50000,N,M<=50000
a<=b<=N
1操作中abs(c)<=N
2操作中abs(c)<=Maxlongint
分析:
按照CDQ分治的做法,是答案当做关键字来分治,由于答案最终在-n~n之间,这里首先需要一个转化,将区间第c大变成第c小,只需要将每个数变成n-c+1。
对于这类操作类的题目 ,CDQ分治的做法首先要保证的是操作的顺序,接下来以答案为关键字,例如询问结果在L~R之间的操作2,分成两部分递归L~m,m+1~R处理,#11对于操作1如果添加的数<=m,则加入到相应的位置区间;#12否则说明操作1影响答案在右半区间m+1~R的操作2。然后对于每个操作2查询当前位置区间有多少个数,表示该区间<=m已经有多少个数(#21),如果(#22)数目tmp > c (查询数目),说明答案应该在m+1~R,否则在L~m。然后将操作1中影响答案在左半部分的(编号#11)和操作2中答案在左半部分的(#21)集中在一起左半部分,剩下的集中在右半部分。然后递归处理答案在左半部分和右半部分的。每次进行子区间的递归时都将操作分成了2部分,表示不同区间被对应不同的操作。
具体成段增加一个值和查询某一段的和用到了树状数组,也可以用线段树,不过我觉得树状数组解法简洁有力,orz,
上一下原文树状数组链接
http://www.cnblogs.com/lazycal/archive/2013/08/05/3239304.html
我的代码:
bzoj好像不能用cout输出一个表达式,会RE!
1 /*Time 2014 08 31 , 19:26 2 3 */ 4 #include <bits/stdc++.h> 5 #define in freopen("solve_in.txt", "r", stdin); 6 #define bug(x) printf("Line %d : >>>>>>>\n", (x)); 7 8 using namespace std; 9 typedef long long LL; 10 const int maxn = 50000 + 100; 11 LL x1[maxn][2], x2[maxn][2], ans[maxn]; 12 int cnt; 13 14 struct Node 15 { 16 int l, r, type; 17 LL c; 18 int id; 19 } q[maxn]; 20 int rk[maxn], t1[maxn], t2[maxn]; 21 int n, m; 22 LL query(LL a[][2], int x) 23 { 24 LL res = 0; 25 for(; x > 0; x -= (x&(-x))) 26 { 27 if(a[x][0] == cnt) res += a[x][1]; 28 } 29 return res; 30 } 31 LL query(int l, int r) 32 { 33 return query(x1, l)*(r-l+1)+ (r+1)*(query(x1, r)-query(x1, l)) - (query(x2, r)-query(x2, l)); 34 } 35 void add(LL a[][2], int x, LL c) 36 { 37 for(; x <= n; x += ((-x)&x)) 38 { 39 if(a[x][0] == cnt) a[x][1] += c; 40 else a[x][0] = cnt, a[x][1] = c; 41 } 42 } 43 void add(int l, int r, int c) 44 { 45 add(x1, l, c); 46 add(x2, l, (LL)l*c); 47 add(x1, r+1, -c); 48 add(x2, r+1, (LL)(r+1)*(-c)); 49 } 50 void solve(int ll, int rr, int l, int r) 51 { 52 if(l > r) return; 53 if(ll == rr) 54 { 55 for(int i = l; i <= r; i++) 56 if(q[rk[i]].type == 2) 57 { 58 ans[rk[i]] = ll; 59 } 60 return; 61 } 62 int m1 = (ll+rr)>>1, m2 = (l+r)>>1; 63 cnt++; 64 t1[0] = t2[0] = 0; 65 for(int i = l; i <= r; i++) 66 { 67 if(q[rk[i]].type == 1) 68 { 69 if(q[rk[i]].c <= m1) 70 { 71 add(q[rk[i]].l, q[rk[i]].r, 1); 72 t1[++t1[0]] = rk[i]; 73 } 74 else 75 { 76 t2[++t2[0]] = rk[i]; 77 } 78 } 79 else 80 { 81 LL xx = query(q[rk[i]].l, q[rk[i]].r); 82 if(xx < (LL)q[rk[i]].c) 83 { 84 q[rk[i]].c -= xx; 85 t2[++t2[0]] = rk[i]; 86 } 87 else 88 { 89 t1[++t1[0]] = rk[i]; 90 } 91 } 92 } 93 m2 = l+t1[0]-1; 94 95 for(int i = l; i <= r; i++) 96 { 97 if(i <= m2) 98 { 99 rk[i] = t1[i-l+1]; 100 } 101 else 102 { 103 rk[i] = t2[i-m2]; 104 } 105 } 106 solve(ll, m1, l, m2); 107 solve(m1+1, rr, m2+1, r); 108 } 109 int main() 110 { 111 112 scanf("%d%d", &n, &m); 113 for(int i = 1; i <= m; i++) 114 { 115 rk[i] = i; 116 scanf("%d%d%d%lld", &q[i].type, &q[i].l, &q[i].r, &q[i].c); 117 if(q[i].type == 1) q[i].c = (LL)n-q[i].c+1; 118 q[i].id = i; 119 } 120 solve(1, 2*n+1, 1, m); 121 for(int i = 1; i <= m; i++) 122 { 123 if(q[i].type == 2) 124 { 125 printf("%d\n", n-ans[i]+1); 126 } 127 } 128 return 0; 129 }