[HEOI2016/TJOI2016]序列 CDQ分治
题解:
首先我们观察一下,如果一个点对(j, i),
要符合题中要求要满足哪些条件?
首先我们设 j < i
那么有:
j < i
max[j] < v[i]
v[j] < min[i]
(注意下面两个式子都是用的v[i],v[j],,,而不是i , j。。。之前因为这个问题纠结了很久,其实我也不知道我在纠结什么。。。)
这三个式子是不是很眼熟?
如果式子变成:
j < i
max[j] < max[i]
min[j] < min[i]
是不是就和三维偏序一模一样了?
但是还是略有区别,不过区别不大,所以我们考虑一个变种的三维偏序。
在之前的板子中我用的是归并排序。
但由于这个题目式子不同。所以归并排序不在合适
首先CDQ可以保证id的相对有序。
而为了比较max[j] 和 v[i]
我们使用sort,分别对左边按max排序,对右边按id排序。
为了比较v[j] < min[i]
我们使用树状数组,
在满足max[j] < v[i]的条件下,
在树状数组的第v[j]位插入大小为ans[j]的数。
查询时用min[i]查询。
树状数组维护最大值(此时不满足区间减法)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define AC 100100 5 #define getchar() *o++ 6 #define lowbit(x) (x & (-x)) 7 #define D printf("line in %d\n", __LINE__); 8 char READ[5000100], *o = READ; 9 /*变种三维偏序, 10 总的来说就是要保证这三个式子。 11 i < j 12 max[j] < v[i] 13 v[j] < min[i] 14 可以看出的三维偏序是非常类似的, 15 所以也可以用CDQ + 归并 + 树状数组实现。 16 CDQ维护下标,也就是第一个式子 17 归并维护第二个式子, 18 树状数组用于在归并的条件下查询满足第3个式子的贡献。 19 本质上是类似DP的东西? 20 p[i].ans 表示以i为结尾的子序列的最长长度 21 */ 22 /*初始状态下id相对有序,归并维护maxj的顺序 23 但是这个好像和原来的不一样??? 24 原来的是维护maxj < maxi,两边都是一样的属性, 25 所以归并完就排好了,因为它的要求对于左边和右边而言是一样的, 26 都是要按maxj排序,这样的话归并一遍,等到回去的时候这个小块就是按maxj排好序的。 27 就符合上面的要求。 28 但是这个maxj < i,两边是不同的属性,所以不太适用。。。 29 因为这样的话要求小块处理完后左边是按照maxnj排序的,而右边却是按照i排序的。 30 因此这就不便于处理,也就是根本不能排。 31 而且这里左边对右边的贡献是有顺序限制的,因此小块处理也要分开 32 还是要用sort啊......*/ 33 int n, m, ans, maxn; 34 int power[AC], tmp[AC], c[AC]; 35 struct node{ 36 int id, max, min, ans, v; 37 }p[AC]; 38 39 inline int read() 40 { 41 int x = 0; char c = getchar(); 42 while(c > '9' || c < '0') c = getchar(); 43 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 44 return x; 45 } 46 47 inline void upmin(int &a, int b) 48 { 49 if(a > b) a = b; 50 } 51 52 inline void upmax(int &a, int b) 53 { 54 if(b > a) a = b; 55 } 56 57 inline bool cmp1(node a, node b)//按max排序 58 { 59 return a.max < b.max; 60 } 61 62 inline bool cmp2(node a, node b)//v排序 63 { 64 return a.v < b.v; 65 } 66 //按id排序,因为右边块的调用是在处理完左边和左边对右边的贡献后的, 67 inline bool cmp3(node a, node b)//而这时右边已经不在是id有序的状态了,(因为按v排了序),但是又要求id有序,所以还要还原一次 68 { 69 return a.id < b.id; 70 } 71 72 //min由树状数组负责 73 void pre() 74 { 75 int a, b; 76 n = read(), m = read(); 77 for(R i = 1; i <= n; i++) 78 { 79 p[i].id = i; 80 p[i].v = p[i].max = p[i].min = read(); 81 } 82 for(R i = 1; i <= m; i++) 83 { 84 a = read(), b = read(); 85 upmax(p[a].max, b); 86 upmax(maxn, b); 87 upmin(p[a].min, b); 88 } 89 } 90 91 inline void add(int x, int w)//维护前缀最大值的树状数组(不符合区间减法) 92 { 93 for(R i = x; i <= maxn; i += lowbit(i)) upmax(c[i], w);//维护最大值 94 }//因为维护的格子是以v[i]为基础的,存的东西是ans[i],所以上界自然是max(v[i]) 95 96 inline int sum(int x)//查询前缀最大值 97 { 98 int ans = 0; 99 for(R i = x; i ; i -= lowbit(i)) upmax(ans, c[i]); 100 return ans; 101 } 102 103 inline void clear(int x) 104 { 105 for(R i = x; i <= maxn; i += lowbit(i)) c[i] = 0; 106 } 107 108 void CDQ(int l, int r) 109 { 110 if(l < r) 111 { 112 int mid = (l + r) >> 1; 113 CDQ(l, mid); 114 sort(p + l, p + mid + 1, cmp1);//按max排序 115 sort(p + mid + 1, p + r + 1, cmp2);//按v排序 116 int i = l, j = mid + 1; 117 while(j <= r)//因为要靠这个代替内层循环,所以这个要用||,但是又不能直接用||,因为会导致死循环(i一直不符合条件) 118 {//j一直往后走,因此直接判断j,相当于外层只是控制j的 119 while(p[i].max <= p[j].v && i <= mid) add(p[i].v, p[i].ans), ++i; 120 upmax(p[j].ans, sum(p[j].min) + 1), ++j;//还要加上自己,因为i往后移动了,所以这个时候再判断大小就不对了,所以干脆强制转移 121 } //让外层循环代替内层循环 122 for(R k = l; k <= i; k++) clear(p[k].v);//error!!!放进去了多少就拿出来多少,不然就浪费了 123 sort(p + mid + 1, p + r + 1, cmp3);//还原id 124 CDQ(mid + 1, r);//因为这个要统计最长长度,是有先后顺序的,陌上花开是在统计个数,是可以允许没有顺序的 125 } 126 else upmax(p[l].ans, 1);//最少也是1了 127 } 128 129 void work() 130 { 131 for(R i = 1; i <= n; i++) upmax(ans, p[i].ans); 132 printf("%d\n", ans); 133 // printf("time used... %lf\n", (double)clock()/CLOCKS_PER_SEC); 134 } 135 136 int main() 137 { 138 // freopen("in.in", "r", stdin); 139 fread(READ, 1, 5000000, stdin); 140 pre(); 141 CDQ(1, n); 142 work(); 143 // fclose(stdin); 144 return 0; 145 }