[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;
 13 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 }
View Code

猜你喜欢

转载自www.cnblogs.com/ww3113306/p/9821675.html