[JXOI2017]顏色 線段樹掃描線 + 單調棧
~~~題面~~~
題解:
首先題目要求刪除一些顏色,換個說法就是要求保留一些顏色,那麽觀察到,如果我們設ll[i]和rr[i]分別表示顏色i出現的最左邊的那個點和最右邊的那個點,那麽題目就是在要求我們選出的區間要滿足區間[l, r]內所有顏色的max(rr[i]) <= r,並且min(ll[i]) >= l. 因為是區間相關的問題,又涉及到左右端點,因此我們考慮掃描線,那麽考慮如何維護它。
因為每個顏色的ll[i]和rr[i]可以看做構成了一個區間,那麽現在已經進入線段樹的節點就分2種情況。
1,區間右端點超過當前右端點:
我們找到離當前右端點最近的點x,使得它代表的區間和右端點關系如上圖所示,那麽顯然這個點x以及它之前的左端點都是不能取的,又因為這個點x是離當前右端點最近的滿足條件的點,所以這個點之後都不會因為這個條件而產生沖突,即在這個點後面的,在當前右端點前面的,都滿足了右端點的限制。那麽我們只需要再滿足左端點的限制,然後查詢(x, i)對答案的貢獻,其中i是當前枚舉的右端點。那麽我們如何找這個點呢?
觀察到一個性質,在後面出現的點的右端點>= 前面出現的點的右端點 的情況下,在後面出現的肯定會更優;因此我們只需要維護一個右端點單調遞減的單調棧即可,如果有一個右端點更右出現了,那麽肯定會比之前右端點比它小(相等)的點更優,但是不能彈掉右端點比它大的,因為隨著右端點的增大,可能這個點就失效了,但之前右端點比它大的點還是有效的。
2,區間右端點不超過當前右端點。
對於這種情況而言,顯然我們要麽把這個區間全部取了,要麽一點都不取。因此不合法的左端點就是(ll, rr],把這段賦0即可。觀察到因為我們是賦0,不是-1,所以無法撤銷,但是這是沒有關系的,因此如果在當前右端點下,這個區間已經不超過它了,那麽以後隨著右端點的增大,就更不可能超過了,因此不需要撤回。同時也正是因為無法撤回,所以上面那種情況需要單調棧而不是直接修改,因為上面那種情況,隨著右端點的增大,是會變成第二種情況的。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define AC 301000 5 #define ac 1200100 6 #define LL long long 7 8 /*用棧來維護查詢的區間(因為每次的區間不同,而且都只要修改前綴,所以完全不用每次修改 9 (修改了就無法轉移到下一個右端點了,因為不滿足區間減法,無法撤銷), 10 只需要查詢指定區間內的就可以了*/ 11 12 int n, tot, T;View Code13 LL ans; 14 int ll[AC], rr[AC], color[AC]; 15 int s[AC], top;//棧 16 int tree[ac], lazy[ac], l[ac], r[ac];//線段樹 17 struct co{ 18 int color, id; 19 }p[AC]; 20 21 struct seg{ 22 int l, r; 23 }line[AC]; 24 25 bool z[AC]; 26 27 inline int read() 28 { 29 int x = 0;char c = getchar(); 30 while(c > ‘9‘ || c < ‘0‘) c = getchar(); 31 while(c >= ‘0‘ && c <= ‘9‘) x = x * 10 + c - ‘0‘, c = getchar(); 32 return x; 33 } 34 35 inline bool cmp1(seg a, seg b) 36 { 37 return a.r < b.r; 38 } 39 40 inline bool cmp(co a, co b) 41 { 42 if(a.color != b.color) return a.color < b.color; 43 else return a.id < b.id; 44 } 45 46 inline void pushdown(int x) 47 { 48 if(l[x] != r[x] && lazy[x]) 49 { 50 int ll = x * 2, rr = ll + 1; 51 tree[ll] = tree[rr] = lazy[x] = 0; 52 lazy[ll] = lazy[rr] = 1; 53 } 54 } 55 56 inline void update(int x) 57 { 58 tree[x] = tree[x * 2] + tree[x * 2 + 1]; 59 } 60 61 void build(int x, int ll, int rr) 62 { 63 l[x] = ll, r[x] = rr, lazy[x] = 0; 64 if(ll == rr) {tree[x] = 1; return ;} 65 int mid = (ll + rr) >> 1; 66 build(x * 2, ll, mid), build(x * 2 + 1, mid + 1, rr); 67 update(x); 68 } 69 70 void change(int x, int ll, int rr) 71 { 72 pushdown(x); 73 if(l[x] == ll && r[x] == rr) {tree[x] = 0, lazy[x] = 1; return ;} 74 int mid = (l[x] + r[x]) >> 1; 75 if(rr <= mid) change(x * 2, ll, rr); 76 else if(ll > mid) change(x * 2 + 1, ll, rr); 77 else change(x * 2, ll, mid), change(x * 2 + 1, mid + 1, rr); 78 update(x); 79 } 80 81 void find(int x, int ll, int rr) 82 { 83 pushdown(x); 84 if(l[x] == ll && r[x] == rr){ans += tree[x]; return ;} 85 int mid = (l[x] + r[x]) >> 1; 86 if(rr <= mid) find(x * 2, ll, rr); 87 else if(ll > mid) find(x * 2 + 1, ll, rr); 88 else find(x * 2, ll, mid), find(x * 2 + 1, mid + 1, rr); 89 update(x); 90 } 91 92 void pre() 93 { 94 n = read(); 95 for(R i = 1; i <= n; i ++) color[i] = p[i].color = read(), p[i].id = i; 96 sort(p + 1, p + n + 1, cmp); 97 for(R i = 1; i <= n; i ++) 98 if(p[i].color != p[i - 1].color) 99 rr[p[i - 1].color] = p[i - 1].id, ll[p[i].color] = p[i].id; 100 rr[p[n].color] = p[n].id; 101 for(R i = 1; i <= n; i ++) 102 if(ll[i]) line[++tot] = (seg){ll[i], rr[i]}; 103 sort(line + 1, line + tot + 1, cmp1); 104 } 105 106 void init() 107 { 108 memset(ll, 0, sizeof(ll)), memset(rr, 0, sizeof(rr)); 109 tot = ans = top = 0; 110 } 111 112 void get() 113 { 114 int l = 1; 115 for(R i = 1; i <= n; i ++)//不斷擴大右端點 116 { 117 //printf("!!!%d\n", i); 118 while(top && rr[color[i]] >= rr[color[s[top]]]) -- top;//如果一個點在棧頂右側,且右端點大於等於棧頂,那麽它肯定更優。 119 s[++top] = i;//棧裏面存顏色就夠了, ,,,不,,,還是需要存下標 120 while(top && rr[color[s[top]]] <= i) -- top;//去掉不合法的情況 121 for(; line[l].r <= i && l <= tot; ++ l)//error!!!這裏要用tot,不然的話用n可能會用到一些未被覆蓋的,來自前面的數據的區間 122 if(line[l].l < line[l].r) change(1, line[l].l + 1, line[l].r); 123 if(s[top] + 1 <= i) find(1, s[top] + 1, i); //要有合法的情況才查詢,否則沒有必要查詢 124 } 125 printf("%lld\n", ans); 126 } 127 128 void work() 129 { 130 T = read(); 131 while(T --) init(), pre(), build(1, 1, n), get(); 132 } 133 134 int main() 135 { 136 // freopen("color7.in", "r", stdin); 137 work(); 138 // fclose(stdin); 139 return 0; 140 }
[JXOI2017]顏色 線段樹掃描線 + 單調棧