题面链接:点击打开链接
(一)题面:
Description
A string consisting only of parentheses ‘(’ and ‘)’ is called balanced if it is one of the following.
- A string “()” is balanced.
- Concatenation of two balanced strings are balanced.
- When a string s is balanced, so is the concatenation of three strings “(”, s, and “)” in this order.
Note that the condition is stronger than merely the numbers of ‘(’ and ‘)’ are equal. For instance, “())(()” is not balanced.
Your task is to keep a string in a balanced state, under a severe condition in which a cosmic ray may flip the direction of parentheses.
You are initially given a balanced string. Each time the direction of a single parenthesis is flipped, your program is notified the position of the changed character in the string. Then, calculate and output the leftmost position that, if the parenthesis there is flipped, the whole string gets back to the balanced state. After the string is balanced by changing the parenthesis indicated by your program, next cosmic ray flips another parenthesis, and the steps are repeated several times.
Input
The input consists of a single test case formatted as follows.
The first line consists of two integers N and Q ( 2 ≤ N ≤ 300000, 1 ≤ Q ≤ 150000). The second line is a string s of balanced parentheses with length N. Each of the following Q lines is an integer qi ( 1 ≤ qi ≤ N) that indicates that the direction of the qi-th parenthesis is flipped.
Output
For each event qi, output the position of the leftmost parenthesis you need to flip in order to get back to the balanced state.
Note that each input flipping event qi is applied to the string after the previous flip qi − 1 and its fix.
Sample Input
6 3 ((())) 4 3 1 20 9 ()((((()))))()()()() 15 20 13 5 3 10 3 17 18
Sample Output
2 2 1 2 20 8 5 3 2 2 3 18
(二)题目大意:
给出一个只由字符'('和')'组成的字符串,给出了一个字符是平衡字符串的定义(具体看题面说明),求当修改了某一个位置的字符时(由'('变为')'或者由')'变为'(')时,应该再修改哪一个字符,使字符再次变得平衡。输出可以符合条件的最小的位置编号。
(三)解题思路:
- 将'('看作1,')'看作-1,那么当一个字符串平衡时,必有:字符的所有前缀和均大于0。因为如果某一个前缀和小于0,则说明到当前为止')'的数目比'('的数目多,那么当前不可能再平衡。那么我们可以定义等价条件:一个字符串是平衡串当且仅当这个串的所有前缀和均大于等于零,且字符串所有元素的权值和为零。
- 若我们将某一位(id)进行改变:
(Ⅰ)若该位为'(',则由'('->')'的变化相当于我们将位置[id,N]的前缀和均减小了2(由1 -> -1)。由于我们需要输出最小的符合条件的位置,那么我们只要将位置[1,id]中第一个')'改为'('即可满足所有前缀和均大于等于0的条件。
(Ⅱ)若该位为')',则由'('->')'的变化相当于我们将位置[id,N]的前缀和均增加了2(由-1 -> 1)。这样修改虽然满足了"字符串的所有前缀和均大于等于零",但是不满足整个字符串的权值和为0,故我们需要在不破坏"字符串的所有前缀和均大于等于零"的条件下,使字符串所有的权值和为0。由于修改的时候我们会将某一位(假设是第K位)的权值-2。那么对于位置[K,id],它们的前缀和必须>=2,否则-2以后某些位置前缀和将会小于0。这样一来,我们要找的就是从id位置往前找最前面一个前缀和>=2的位置(且中间位置的前缀和均>=2)。 - 考虑情况(Ⅰ),我们求第一次出现')'即权值为-1的位置即可。
- 考虑情况(Ⅱ),我们可以维护任意一个区间的前缀和的最小值,然后二分区间[1,id],找到从id出发,最前面可以满足条件的位置。
- 两种情况均可以用线段树维护,套上二分,复杂度为O(n*lognlogn)。
(四)具体代码:
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<queue>
#include<cmath>
#include<map>
#include<algorithm>
#define LL long long
#define MOD 1000000007
using namespace std;
const int maxn=3e5+10;
char str[maxn];
int N,Q,ID;
struct Tree{
int L,R,sum,state,lazy;
int mid(){return (L+R)>>1;}
}tree[maxn<<2];
void Push_down(int i){
if(tree[i].lazy){
tree[i<<1].lazy+=tree[i].lazy;
tree[i<<1].sum+=tree[i].lazy;
tree[i<<1|1].lazy+=tree[i].lazy;
tree[i<<1|1].sum+=tree[i].lazy;
tree[i].lazy=0;
}
}
void BuildTree(int l,int r,int i){
tree[i].L=l;tree[i].R=r;
tree[i].sum=tree[i].lazy=0;tree[i].state=1e9;
if(l==r)return;
int m=tree[i].mid();
BuildTree(l,m,i<<1);
BuildTree(m+1,r,i<<1|1);
}
void Update_idx(int id,int i,int val){ //改变位置id的状态
if(tree[i].L==tree[i].R&&tree[i].L==id){
tree[i].state=val;
return;
}
int m=tree[i].mid();
if(id>m)Update_idx(id,i<<1|1,val);
else Update_idx(id,i<<1 ,val);
tree[i].state=min(tree[i<<1].state,tree[i<<1|1].state);
}
void Update_pre(int l,int r,int i,int val){ //更新区间的各个前缀和
int L=tree[i].L,R=tree[i].R;
if(l<=L&&R<=r){
tree[i].sum+=val;
tree[i].lazy+=val;
return;
}
Push_down(i);
int m=tree[i].mid();
if(l>m)Update_pre(l,r,i<<1|1,val);
else if(r<=m)Update_pre(l,r,i<<1,val);
else{
Update_pre(l,m,i<<1,val);
Update_pre(m+1,r,i<<1|1,val);
}
tree[i].sum=min(tree[i<<1].sum,tree[i<<1|1].sum);
}
int Query_min(int l,int r,int i){ //查询区间[l,r]的最小值
int L=tree[i].L,R=tree[i].R;
if(l<=L&&R<=r)return tree[i].state;
int m=tree[i].mid();
if(l>m)return Query_min(l,r,i<<1|1);
else if(r<=m)return Query_min(l,r,i<<1);
else return min(Query_min(l,m,i<<1),Query_min(m+1,r,i<<1|1));
}
int Query_sum(int l,int r,int i){ //查询区间[l,r]各个位置前缀和的最最小值
int L=tree[i].L,R=tree[i].R;
if(l<=L&&R<=r)return tree[i].sum;
Push_down(i);
int m=tree[i].mid(),res=1e9;
if(l>m)res=Query_sum(l,r,i<<1|1);
else if(r<=m)res=Query_sum(l,r,i<<1);
else res=min(Query_sum(l,m,i<<1),Query_sum(m+1,r,i<<1|1));
tree[i].sum=min(tree[i<<1].sum,tree[i<<1|1].sum);
return res;
}
int main(){
freopen("in.txt","r",stdin);
while(~scanf("%d%d",&N,&Q)){
BuildTree(1,N,1);
scanf("%s",str+1);
for(int i=1;i<=N;i++){
if(str[i]=='('){
Update_idx(i,1,1);
Update_pre(i,N,1,1);
}
else{
Update_idx(i,1,-1);
Update_pre(i,N,1,-1);
}
}
while(Q--){
scanf("%d",&ID);
if(str[ID]=='('){
str[ID]=')';
Update_idx(ID,1,-1);
Update_pre(ID,N,1,-2);
int l=1,r=ID;
while(l<=r){
int mid=(l+r)>>1;
if(Query_min(l,mid,1)<0)r=mid-1;
else l=mid+1;
}
str[l]='(';
Update_idx(l,1,1);
Update_pre(l,N,1,2);
printf("%d\n",l);
}
else{
str[ID]='(';
Update_idx(ID,1,1);
Update_pre(ID,N,1,2);
int l=1,r=ID;
while(l<=r){
int mid=(l+r)>>1;
if(Query_sum(mid,r,1)>=2)r=mid-1;
else l=mid+1;
}
str[l]=')';
Update_idx(l,1,-1);
Update_pre(l,N,1,-2);
printf("%d\n",l);
}
}
}
return 0;
}
(五)总结:
当时思路是想到了,并且实现了第一种情况,但是实现不了第二种情况。还是题量不够、各种数据结构用地不熟练的缘故吧...。