2014 - 2015东京区域赛:Problem G Flipping Parentheses【线段树变种】

本文介绍了一种使用前缀和及线段树解决括号匹配问题的算法,通过维护前缀和数组最小值来快速查找需要翻转的括号,实现括号串合法性检查。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

比赛链接
本题题意:给你一个合法的括号串,比如(())()()()()
然后会改变q次,每次改变一个括号的朝向,然后你要输出一个数ans表示你要翻转第ans个括号的朝向使得新的串仍然是合法的;同时如果存在多个ans,请对最左边的括号操作。即要求找一个leftest 下标翻转后能保持括号信息正常;

括号匹配容易想到栈和前缀和。栈排除,因为有q次操作;前缀和设左括号为+1,右括号为-1,于是能得到一个前缀和串;
容易知道,括号串合法的充要条件是前缀和串处处不为负数;

又双叒叕容易想到:
如果翻转了pos位置的一个左括号,也就是+1变成了-1,也就是说相当于Pre[pos,N]每一个数都-2。然后我们就得翻转一个Loc<=pos,使得Loc为右括号,被你翻转成了左括号,于是相当于[Loc,pos-1]的数+2,所以自然而然的,Loc就要求是最左边的一个右括号即可,用一个set维护吧那就。

如果翻转了pos位置的一个右括号,也就是-1变成了+1,那同样的找一个Loc位置为左括号的下标,翻转为右括号,如此相当于[Loc,pos-1]-2,如此得到的就是最左边的Loc,使得Loc到pos间的前缀和都是>=2的;于是答案就出来了:二分左端点,而且最重要的是最左边的连续的>=2的点一定是左括号,因为Loc-1的前缀和一定是<2的,即Loc位置比Loc-1位置多了1,于是Loc一定是左括号。

前缀和维护的自然就是前缀和数组的最小值了,当最小值>=2说明整个区间的数都>=2;
于是二分左端点时间log,线段树也log,此题 N l o g 2 Nlog^2 Nlog2做完;
Code:

#include <bits/stdc++.h>
const double eps = 1e-6;
const double PI = acos(-1);
const int INF = 0x3f3f3f3f;
#define ms(a,k) memset(a,k,sizeof(a))
#define X first
#define Y second
#define pii pair<int ,int >
#define lowbit(a) a&(-a)
typedef long long ll ;
typedef unsigned long long ull;
using namespace std;
inline void read(int &x){scanf("%d",&x);}
const int mod = 998244353;
const int maxn = 300000+10;
int tr[maxn*4],lazy[maxn*4];//tr维护的是区间最小值;
set<int >loc;
char s[maxn];
int pre[maxn];
void build(int k,int l,int r)
{
	tr[k] = INF;
	if(l==r){tr[k] = pre[l];return ;}
	int mid = l+r>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	tr[k] = min(tr[k<<1] , tr[k<<1|1]);
}
void down(int k,int l,int r)
{//lazy是区间加
	int mid = l+r>>1;
	lazy[k<<1] += lazy[k];
	lazy[k<<1|1] += lazy[k];
	tr[k<<1] += lazy[k];
	tr[k<<1|1] += lazy[k];
	lazy[k] = 0;
}
void add(int k,int l,int r,int L,int R,int v)
{
	if(L>R)return ;
	if(L<=l&&r<=R){tr[k]+=v;lazy[k]+=v;return ;}
	int mid = l+r>>1;
	down(k,l,r);
	if(L<=mid)add(k<<1,l,mid,L,R,v);
	if(R>=mid+1)add(k<<1|1,mid+1,r,L,R,v);
	tr[k] = min(tr[k<<1],tr[k<<1|1]);
}
int query(int k,int l,int r,int L,int R)
{
	//printf("%d %d %d %d %d\n",k,l,r,L,R);
	if(L<=l&&r<=R){return tr[k];}
	int mid = l+r>>1;
	down(k,l,r);
	int res =INF;
	if(L<=mid){res = min(res, query(k<<1,l,mid,L,R)) ;}
	if(mid+1<=R){res = min(res, query(k<<1|1,mid+1,r,L,R)) ;}
	return res;
}
int main()
{
	int n,q;read(n);read(q);
	scanf("%s",s+1);
	for(int i =1;i<=n;++i)
	{
		if(s[i]=='('){pre[i] = pre[i-1]+1; }
		else {pre[i] = pre[i-1]-1;loc.insert(i);}
	}
	build(1,1,n);
	while(q--)
	{
		int pos ;read(pos);
		if(s[pos] == '(' )
		{//找到最左边的右括号翻转成为左括号即可;
			loc.insert(pos);
			int tar = *loc.begin() ;
			loc.erase(tar);
			s[pos] = ')';
			s[tar] = '(';
			add(1,1,n,tar,pos-1,2);
			printf("%d\n",tar);
		}
		else
		{//找到一个最左边的tar满足本身为左括号->右括号,且满足min[tar, pos-1]>=2;
			int l = 0,r = pos;
			while(l+1<r)//r作为答案
			{
				int mid = l+r>>1;
				if( query(1,1,n,mid,pos-1) >= 2 )r = mid;
				else l = mid;
			}
			int tar = r;//如果连r都不是答案,那么答案只能是pos了
			if(tar == pos){printf("%d\n",tar);continue;}
			loc.erase(pos);
			loc.insert(tar);
			s[pos] = '(';
			s[tar] = ')';
			add(1,1,n,tar,pos-1,-2);
			printf("%d\n",tar);
		}
		//printf("%s\n",s+1);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值