[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 }

原文地址:https://www.cnblogs.com/ww3113306/p/9821675.html

时间: 2024-08-30 09:03:51

[JXOI2017]颜色 线段树扫描线 + 单调栈的相关文章

Educational Codeforces Round 61 (Rated for Div. 2) G(线段树,单调栈)

#include<bits/stdc++.h>using namespace std;int st[1000007];int top;int s[1000007],t[1000007];int mx[4000007];int sum[4000007];int head[1000007],to[2000007],nex[2000007];int n,k;int a[10000077];int dfn;int tot;void pushup(int rt){    mx[rt]=max(mx[rt

Codeforces 780G Andryusha and Nervous Barriers 线段树套set || 线段树套单调栈

Andryusha and Nervous Barriers 问题本质我们只需要找出每个线段两端下面第一个碰到的是哪个线段就好啦. 按照 h 排序我们就能用线段树套set 不管维护什么都能维护出这个东西,但是 如果set里维护 u + s的话,就能优化成单调栈, 好优秀啊. #include<bits/stdc++.h> #define LL long long #define LD long double #define ull unsigned long long #define fi f

HDU 4419 Colourful Rectangle --离散化+线段树扫描线

题意: 有三种颜色的矩形n个,不同颜色的矩形重叠会生成不同的颜色,总共有R,G,B,RG,RB,GB,RGB 7种颜色,问7种颜色每种颜色的面积. 解法: 很容易想到线段树扫描线求矩形面积并,但是如何维护每种颜色的长度着实让我伤透了脑筋.后来看了一位朋友的题解,才幡然醒悟. 开始想到了用二进制表示颜色,R用001表示,G用010表示,B用100表示.那么就可以用十进制1~7表示7种不同颜色了. 维护 cov[rt][1~3] 表示此区间内3种原色各有多少个, Len[rt][i]表示每种颜色的长

sgu316Kalevich Strikes Back(线段树+扫描线)

做法:总体想法是求出一个矩形的面积以及它所包含的矩形,然后用自己的面积减掉所包含的.主要问题是怎样求解它所包含的矩形. 因为是没有相交点的,可以利用扫描线的方法去做,类似染色,当前段如果是x色,也就是第x个矩形,那么再把他染成y颜色时,说明x包含y,而当扫到y的上边时,这一段又恢复到x色.标记一下被包含的矩形,记录所包含的矩形. 因为会有恢复染色操作,up需要时时更新,左儿子和右儿子一样颜色时就可以合并为一段. 1 ; 2 } 3 void down(int w) 4 { 5 if(s[w]!=

【BZOJ】1382: [Baltic2001]Mars Maps (线段树+扫描线)

1382: [Baltic2001]Mars Maps Time Limit: 5 Sec  Memory Limit: 64 MB Description 给出N个矩形,N<=10000.其坐标不超过10^9.求其面积并 Input 先给出一个数字N,代表有N个矩形. 接下来N行,每行四个数,代表矩形的坐标. Output 输出面积并 Sample Input 2 10 10 20 20 15 15 25 30 Sample Output 225 本以为是傻逼题,没想到不容易啊- 线段树+扫描

BZOJ 4059 Cerc2012 Non-boring sequences 线段树+扫描线

题目大意:定义一个序列为[不无聊的]当且仅当这个序列的任意一个区间都存在一个数只出现过一次,给定一个序列,要求判断这个序列是否是[不无聊的] 定义lasti表示第i个元素上一次出现的位置(第一次出现则为0),nexti表示第i个元素下一次出现的位置(最后一次出现则为n+1),那么这个元素能成为某个区间仅出现一次的数,当且仅当这个区间的左端点在[lasti+1,i]之间,右端点在[i,nexti?1]之间 我们可以将区间的左右端点放在二维平面上,那么一个元素产生的贡献是一个矩形,我们要确定的是所有

hdu1255 覆盖的面积 线段树-扫描线

矩形面积并 线段树-扫描线裸题 1 #include<stdio.h> 2 #include<string.h> 3 #include<algorithm> 4 #include<math.h> 5 using namespace std; 6 const int maxm=2e3+5; 7 const double eps=1e-5; 8 9 struct seg{ 10 double x,y1,y2; 11 int c; 12 bool operator

线段树+扫描线 HDOJ 5091 Beam Cannon

题目传送门 1 /* 2 题意:给出若干个点的坐标,用一个矩形去覆盖,问最多能覆盖几个点 3 线段树+扫描线:思路是先建一棵以[y, y + h]的树,左右儿子[x, x + w] 4 以这棵树为范围,从左到右扫描,更新点数,x的+1, x+w的-1(超过矩形范围) 5 ans = 每次更新时所覆盖点数的最大值 6 */ 7 #include <cstdio> 8 #include <algorithm> 9 #include <iostream> 10 #includ

POJ 3277 City Horizon(线段树+扫描线+离散化)

题目地址:POJ 3277 水题..稍微处理一下然后用求面积并的方法求即可. 代码如下: #include <iostream> #include <cstdio> #include <string> #include <cstring> #include <stdlib.h> #include <math.h> #include <ctype.h> #include <queue> #include <