CF524F And Yet Another Bracket Sequence 题解

传送门

天天做简单题,不能要了。

首先易得最优操作方案一定是先做 \(2\) 操作再做 \(1\) 操作,'(' 一定插在字符串的开头,')' 一定插在字符串的末尾。

这个问题等价于求该串字典序最小的的循环同构串,使得其在开头或结尾插入最少的括号字符使得左括号个数与右括号个数相等后,其为合法的括号串。

即转一段然后在开头加若干左括号,或转一段然后在结尾加若干右括号。最优情况加入的括号个数为 \(|\text{左括号个数}-\text{右括号个数}|\)

易证明如果一个字符串的左括号个数与右括号个数相等,一定存在一个循环同构串使得其在添加 \(|\text{左括号个数}-\text{右括号个数}|\) 个左/右括号后为合法括号串。

直接枚举 \(2\) 操作的个数,判断得到的循环同构串是否为合法的括号串(用 multiset 维护最小前缀和)。比较两个循环同构串的字典序可以用二分+hash,时间复杂度 \(O(n\log n)\)

参考代码:

#include<bits/stdc++.h>
#define ll long long
#define mxn 2000003
#define md 1000000007
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define rept(i,a,b) for(int i=a;i<b;++i)
#define drep(i,a,b) for(int i=a;i>=b;--i)
using namespace std;
int n,t,w,ps,d[mxn];
ll c[mxn],h[mxn];
char s[mxn];
multiset<int>st,s2;
inline ll get(int l,int r){
	return (h[r]-h[l-1]*c[r-l+1]%md+md)%md;
}
bool check(int x,int y){
	if(get(x,x+n-1)==get(y,y+n-1))return 0;
	int l=1,r=n;
	while(l<r){
		int mid=(l+r)>>1;
		if(get(x,x+mid-1)==get(y,y+mid-1))l=mid+1;
		else r=mid;
	}
	return s[x+l-1]<s[y+l-1];
}
signed main(){
	scanf("%s",s+1),n=strlen(s+1);
	rep(i,1,n)s[n+i]=s[i];
	c[0]=1;
	rep(i,1,n<<1){
		c[i]=c[i-1]*2%md;
		h[i]=(h[i-1]*2+s[i]-'(')%md;
	}
	rep(i,1,n)d[i]=d[i-1]+(s[i]=='('?1:-1);
	rep(i,1,n)st.insert(d[i]);
	ps=-1;
	rep(i,1,n){
		if(*st.begin()-d[i-1]>=min(d[n],0)&&(s2.empty()||*s2.begin()+d[n]-d[i-1]>=min(d[n],0))){
			if(ps==-1||check(i,ps))ps=i;
		}
		st.erase(st.find(d[i]));
		s2.insert(d[i]);
	}
	if(d[n]<0)rept(i,0,-d[n])putchar('(');
	rep(i,ps,n)putchar(s[i]);
	rept(i,1,ps)putchar(s[i]);
	if(d[n]>0)rept(i,0,d[n])putchar(')');
	return 0;
}
posted @ 2023-11-13 20:26  zifanwang  阅读(3)  评论(0编辑  收藏  举报  来源