Gym 100803G 线段树

好长时间前做的题,来补一下题解。

给出括号化的序列,每次改变一个括号方向,求出下标p,是的改变p处的括号方向可以使括号化仍然成立,且p最小。

保证括号化看似和线段树没有联系,我们可以把括号"("表示为1,把括号")"表示为-1,则保证括号化的充要条件就是使数字序列前缀和始终大于等于零

用线段树维护前缀和

查询有两种情况

1. "(" 变成 ")"

这种情况我们只需找到最左边的一个")",就是答案。(可以用一个set维护所有的")"的位置)

2.")" 变成 "("

这种情况我们要找一个最左边的“(”变成”)“,也就是把p处及以后的前缀和都减2,且不能出现负的前缀和。

于是我们就要借助线段树查询前缀和小于2的最右边节点,这样把区间[p + 1, n]减二之后才不会出现负值。

用线段树维护区间最小值,也就是区间最小前缀和。线段树能往右走就往又走,注意查询过程中区间边界的特判处理。

  1 #include<cstdio>
  2 #include<algorithm>
  3 #include<cstring>
  4 #include<set>
  5 #include<iostream>
  6 #define lson o << 1, L, M
  7 #define rson (o << 1) | 1, M + 1, R
  8 using namespace std;
  9 const int MAXN = 300010;
 10 const int INF = 0x3f3f3f3f;
 11 char str[MAXN];
 12 int num[MAXN];
 13 int minv[MAXN << 2];
 14 int addv[MAXN << 2];
 15 int len;
 16 void pushup(int o, int L, int R) {
 17     minv[o] = 0;
 18     if(R > L) {
 19         minv[o] = min(minv[o << 1], minv[(o << 1) | 1]);
 20     }
 21     minv[o] += addv[o];
 22 }
 23 void update(int p, int v, int o, int L, int R) {
 24     if(p == L && p == R) {
 25         addv[o] = v;
 26         minv[o] = 0;
 27     } else {
 28         int M = L + (R - L) / 2;
 29         if(p <= M) update(p, v, lson);
 30         else update(p, v, rson);
 31
 32     }pushup(o, L, R);
 33 }
 34 void add(int l, int r, int v, int o, int L, int R) {
 35     if(l <= L && r >= R) {
 36         addv[o] += v;
 37     } else {
 38         int M = L + (R - L) / 2;
 39         if(l <= M)add(l, r, v, lson);
 40         if(r > M) add(l, r, v, rson);
 41     }
 42     pushup(o, L, R);
 43 }
 44 int query_min(int l ,int r, int add, int o, int L, int R) {
 45     if(l <= L && r >= R) {
 46         return minv[o] + add;
 47     } else {
 48         int M = L + (R - L) / 2;
 49         int res = INF;
 50         if(l <= M) res = min(res, query_min(l, r, add + addv[o],lson));
 51         if(r > M) res = min(res, query_min(l, r, add + addv[o], rson));
 52         return res;
 53     }
 54 }
 55 int query(int l, int r, int add, int o, int L, int R) { //cout << L << "!!!" << R << endl;
 56     if(L == R) {
 57         if(L == 1) {
 58             if(minv[o] + add >= 2) return 0;
 59             return L;
 60         } else {
 61             return L;
 62         }
 63     }
 64     int M = L + (R - L) / 2;
 65     if(r < M + 1) return query(l, r, add + addv[o], lson);
 66     int minr = query_min(M + 1, min(r, R), 0, 1, 1, len);
 67     if(minr < 2) return query(l, r, add + addv[o], rson);
 68     return query(l, r, add + addv[o], lson);
 69 }
 70 int main() {
 71     int n, q; set<int> fir;
 72     scanf("%d%d", &n, &q);
 73     scanf("%s", str + 1);
 74     len = n;
 75     for(int i = 1 ; i <= len ; i++)
 76         if(str[i] == ‘(‘) num[i] = 1;
 77         else {
 78             num[i] = -1;
 79             fir.insert(i);
 80         }
 81     for(int i = 1 ; i <= len ; i++) num[i] += num[i - 1];
 82     for(int i = 1 ; i <= len ; i++) update(i, num[i], 1, 1, len);
 83     //cout << query_min(1, 1, 0, 1, 1, len) << "!!" << endl;
 84     for(int i = 0 ; i < q ; i++) {
 85         //cout << str + 1 << endl;
 86         int pos; scanf("%d", &pos);
 87         if(str[pos] == ‘(‘) {
 88             str[pos] = ‘)‘;
 89             fir.insert(pos);
 90             int pp = *fir.begin();
 91             fir.erase(fir.begin());
 92             add(pos, len, -2, 1, 1, len);
 93             add(pp, len, 2, 1, 1, len);
 94             printf("%d\n", pp);
 95             str[pp] = ‘(‘;
 96         } else { // str[pos] == ‘)‘
 97             str[pos] = ‘(‘;
 98             add(pos, len, 2, 1, 1, len);
 99             fir.erase(pos);
100             int pp = query(1, pos, 0, 1, 1, len);
101             //cout << pp << "~~" << endl;
102             pp++;
103             str[pp] = ‘)‘;
104             fir.insert(pp);
105             add(pp, len, -2, 1, 1, len);
106             printf("%d\n", pp);
107         }
108     }
109     return 0;
110 }
时间: 2024-10-07 10:41:57

Gym 100803G 线段树的相关文章

K. Random Numbers(Gym 101466K + 线段树 + dfs序 + 快速幂 + 唯一分解)

题目链接:http://codeforces.com/gym/101466/problem/K 题目: 题意: 给你一棵有n个节点的树,根节点始终为0,有两种操作: 1.RAND:查询以u为根节点的子树上的所有节点的权值的乘积x,及x的因数个数. 2.SEED:将节点u的权值乘以x. 思路: 比赛时少看了因数不大于13这句话,然后本题难度增加数倍,肝了两个小时都没肝出来,对不起队友啊,今天的组队训练赛实力背锅…… 这题一眼线段树,由于是对一棵子树进行处理,因此我们采用常规套路,借助dfs序将子树

Codeforces GYM 100114 D. Selection 线段树维护DP

D. Selection Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/gym/100114 Description When selecting files in an application dialog, Vasya noted that he can get the same selection in different ways. A simple mouse click selects a sing

Codeforces Gym 100513F F. Ilya Muromets 线段树

F. Ilya Muromets Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/gym/100513/problem/F Description I Ilya Muromets is a legendary bogatyr. Right now he is struggling against Zmej Gorynych, a dragon with n heads numbered from 1 to nf

Gym 240084E - Correct Bracket Sequence Editor - [线段树]

题目链接:https://codeforces.com/gym/240084/problem/E 题意: 给出一个已经匹配的括号串,给出起始的光标位置(光标总是指向某个括号). 有如下操作: 1.往左移动一下光标: 2.往左移动一下光标: 3.删除当前光标指向的括号,以及和它匹配的那个括号,以及这两个括号之间的所有括号. 要求你给出在做完所有操作后的括号串. 题解: 用线段树维护,每个括号是否存在,存在记为 $1$,被删掉了记为 $0$. 然后我们只需要实现:①区间求和.②区间赋值.③根据 $k

【线段树】Gym - 100507C - Zhenya moves from parents

线段树每个结点维护两个值,分别是这个区间的 负债 和 余钱. 按时间顺序从前往后看的时候,显然负债是单调不减的. 按时间顺序从后往前看的时候,显然余钱也是单调不减的,因为之前如果有余钱,可能会增加现在的余钱,但之前的负债不会减少现在的余钱. 所以线段树的区间合并这样做: 当前区间的负债 = 左区间的负债 + max(右区间的负债 - 左区间的余钱,0): 当前区间的余钱 = 右区间的余钱 + max(左区间的余钱 - 右区间的负债,0): 最后答案就是整个区间的负债. 代码自行脑补.

Gym 101201J Shopping (线段树+取模)

题意:给定 n 个物品,然后有 m 个人买东西,他们有 x 元钱,然后从 l - r 这个区间内买东西,对于每个物品都尽可能多的买,问你最少剩下多少钱. 析:对于物品,尽可能多的买的意思就是对这个物品价格取模,但是对于价格比我的钱还多,那么就没有意义,对取模比我的钱少的,那取模至少减少一半,所以最多只要60多次就可以结束,为了快速找到第一个比我的钱少的,使用线段树. 代码如下: #pragma comment(linker, "/STACK:1024000000,1024000000"

Codeforces Gym 101194G Pandaria (2016 ACM-ICPC EC-Final G题, 并查集 + 线段树合并)

题目链接  2016 ACM-ICPC EC-Final Problem G 题意  给定一个无向图.每个点有一种颜色. 现在给定$q$个询问,每次询问$x$和$w$,求所有能通过边权值不超过w的边走到$x$的点的集合中,哪一种颜色的点出现的次数最多. 次数相同时输出编号最小的那个颜色.强制在线. 求哪种颜色可以用线段树合并搞定. 关键是这个强制在线. 当每次询问的时候,我们先要求出最小生成树在哪个时刻恰好把边权值不超过$w$的边都用并查集合并了. 在做最小生成树的时候每合并两个节点,另外开一个

Hacker Cups and Balls Gym - 101234A 二分+线段树

题目:题目链接 题意:有编号从1到n的n个球和n个杯子. 每一个杯子里有一个球, 进行m次排序操作,每次操作给出l,r. 如果l<r,将[l,r]范围内的球按升序排序, 否则降序排, 问中间位置的数是多少. 思路: 暴力复杂度为m*nlog(n), 不能暴力排序 二分答案, 对于当前mid, 我们将大于等于mid的数记为1, 否则记0, 则排序就是将某个区间的数改为1或0, 通过线段树区间更新可以方便的做到, 对排序后的结果查询判断二分区间应该取左还是取右, 若中间的数是1, 则说明答案大于等于

GYM 101350 F. Monkeying Around(线段树 or 思维)

F. Monkeying Around time limit per test 2.0 s memory limit per test 256 MB input standard input output standard output When the monkey professor leaves his class for a short time, all the monkeys go bananas. N monkeys are lined up sitting side by sid