更改括号与括号匹配
题目描述:
给出一个已经匹配好的(()),长度n~300000, 下面给出Q~150000次修改,每行一个x,每次把x处的东西反转。要求对于每个修改,找出尽量靠左的东西修改y,把y处的反转,使得仍然匹配,输出y。并且随着修改是累积的
题解:
其实是线段树的操作。对于括号,(相当于1,)相当于-1,因为最开始是匹配好的,因此满足匹配只要:从头到尾的和均》=0就好了。那么强制改①(到),会从x到len都-2,之后只需找到最靠左的),改成(就行了,我是用一个set模拟平衡树记录右括号的index,删除的时候是指定的index,所以log,而寻找是begin。②)到(,会从x到len都+2,之后要找从x往前第一个小于2的再加一就是y的位置。并且此时从y到len都是》=2的,因此可以用线段树天生的log分查询第一个小于2的位置。然后就都解决了
重点:
首先思考平时括号匹配的过程,搞出前缀和,然后发现用线段树和set解决的策略。然后是线段树的查询,对于②不能够loglog的二分查询,应该直接log在线段树上跑。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <ctype.h>
#include <limits.h>
#include <cstdlib>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
#include <stack>
#include <set>
#include <bitset>
#define CLR(a) memset(a, 0, sizeof(a))
#define REP(i, a, b) for(int i = a;i < b;i++)
#define REP_D(i, a, b) for(int i = a;i <= b;i++)
typedef long long ll;
using namespace std;
const int maxn = 3e5 + 100;
const int MAXN_NODE = 4*maxn + 10;//线段树的定点是4倍
const int INF = 0;
char s[maxn];
int n, a[maxn], m;
//set<int> lft;
set<int> rgt;//模拟平衡树
int tree[MAXN_NODE], late[MAXN_NODE];
int tN;
void pushUp(int rt)//下面的都已经好了.且一定有下面的
{
int lRt = (rt<<1), rRt = ((rt<<1)|1);
tree[rt] = min(tree[lRt], tree[rRt]);
}
void getTree(int rt)//利用标准的tN来初始化线段树
{
if(rt>=tN)//注意,因为要取最小值,所以很多多余的地方要置最大值
{
if(rt-tN + 1 <= n)
tree[rt] = a[rt - tN + 1];
else
tree[rt] = 1e9;
}
else
{
int lRt = (rt<<1), rRt = ((rt<<1)|1);//不是最后一层的要pushUp
getTree(lRt);
getTree(rRt);
pushUp(rt);
}
}
void initail()
{
CLR(tree);
CLR(late);
getTree(1);
}
void pushDown(int rt)//向下push,此延迟到下一层延迟.
{
if(late[rt] == INF)//不用
return;
int lRt = (rt<<1), rRt = ((rt<<1)|1);
int key = late[rt];//注意修改,但是tree就不用再次更新
late[rt] = INF;
late[lRt] += key;//不要忘了原来也有值
tree[lRt] += key;
late[rRt] += key;
tree[rRt] += key;
}
void change(int L, int R, int key, int rt, int l, int r)
{
if(L <= l && R >= r)//主义同时修改tree
{
late[rt] += key;
tree[rt] += key;
return;
}
pushDown(rt);
int mid = ((l + r)>>1), lRt = (rt<<1), rRt = ((rt<<1)|1);
if(L <= mid)
{
change(L, R, key, lRt, l, mid);
}
if(R >= mid + 1)
{
change(L, R, key, rRt, mid + 1, r);
}
pushUp(rt);//向上push
}
int query(int L, int R, int rt, int l, int r)
{
if(L <= l && R >= r)//全包括
{
return tree[rt];
}
pushDown(rt);//下一次层延迟
int ans = 1e8, mid = ((l + r)>>1), lRt = (rt<<1), rRt = ((rt<<1)|1);
if(L <= mid)
{
ans = min(ans, query(L, R, lRt, l, mid));
}
if(R >= mid + 1)
{
ans = min(ans, query(L, R, rRt, mid + 1, r));
}
return ans;
}
int query_2(int rt, int l, int r)//本题中线段树的关键。查询最靠右的第一个《2的出现位置。从rt为1的自动二分
{
if(l==r)
{
return l;
}
pushDown(rt);//下一次层延迟
int mid = ((l + r)>>1), lRt = (rt<<1), rRt = ((rt<<1)|1);
if(tree[rRt] < 2)
{
return query_2(rRt, mid + 1, r);
}
return query_2(lRt, l, mid);
}
int b_search(int x)//后来没用到,这个是loglog的
{
int l = 1, r = x;
while(l < r)
{
int mid = (l + r)/2;
int key = query(mid, x, 1, 1, tN);
if(key>=2)
{
r = mid;
}
else
{
l = mid + 1;
}
}
return r;
}
void getPre()
{
//lft.clear();
rgt.clear();
n = strlen(s+1);
tN = 1;
while(tN < n)//标准tN的写法
{
tN *= 2;
}
a[0] = 0;
REP_D(i, 1, n)
{
if(s[i]=='(')
{
a[i] = a[i - 1] + 1;
//lft.insert(i);
}
else
{
a[i] = a[i - 1] - 1;
rgt.insert(i);
}
}
initail();
REP_D(i, 1, m)
{
int x;
scanf("%d", &x);
if(s[x]=='(')
{
s[x] = ')';
change(x, n, -2, 1, 1, tN);
//lft.erase(x);
rgt.insert(x);
int y = *(rgt.begin());
printf("%d\n", y);
s[y] = '(';
rgt.erase(y);
//lft.insert(y);
change(y, n, 2, 1, 1, tN);
}
else
{
//printf("--------- %d\n", x);
s[x] = '(';
change(x, n, 2, 1, 1, tN);
//lft.insert(x);
rgt.erase(x);
//int y;
int y = query_2(1, 1, tN) + 1;
//int y = b_search(x);
//printf("yy is %d y is %d\n", yy, y);
//y = *(lft.lower_bound(y));
printf("%d\n", y);
s[y] = ')';
rgt.insert(y);
//lft.erase(y);
change(y, n, -2, 1, 1, tN);
}
}
}
int main()
{
//freopen("7Gin.txt", "r", stdin);
//freopen("7Gout.txt", "w", stdout);
while(scanf("%d%d", &n, &m) != EOF)
{
scanf("%s", s + 1);
getPre();
}
return 0;
}