P6000 [CEOI2016] match

洛谷

对于暴力写法,我们很容易想到一个 \(O(n^2)\) 的暴力。

我们可以先从左到右枚举需要配对的字符,然后从后往前去找到一个合法且相同的字符配对。

对于怎样才算合法,我们通过此部分内部是否合法,以及前面是否有已选择的括号判断。

在括号本身是合法的情况下,我们处理右边是否合法,对于本身存在解的串,左边也必定合法,可以自己模拟证明。

那么我们还需要找到上一次配对的右端,从这里从后往前开始枚举。可以选择使用搜索,这样可以直接继承上一步的端点位置,直接开始枚举。

由于枚举位置较少,除了最后一个测试点都能过。

代码:

#include<bits/stdc++.h>
using namespace std;
char s[100005],ans[100005],p[100005];
bool f[100005];
int len,cnt;
signed main(){
	cin>>s+1;
	len=strlen(s+1);
	if(len%2==1){
		cout<<-1;
		return 0;
	}
	f[len+1]=1;
	for(int i=1;i<=len;i++){
		if(f[i])continue;
		bool flag=0;
		int w;
		for(int j=i+1;j<=len+1;j++){
			if(f[j]){
				w=j;
				break;
			}
		}
		for(int j=w-1;j>i;j--){
			if(cnt==0&&s[j]==s[i]){
				flag=1;
				ans[j]=')';
				f[j]=1;
				break;
			}
			if(!cnt||p[cnt]!=s[j])p[++cnt]=s[j];
			else cnt--;
		}
		if(!flag){
			cout<<-1;
			return 0;
		}
		ans[i]='(';
	}
	cout<<ans+1;
	return 0;
}

时间复杂度的瓶颈明显在于如何找到合适的配对。

第一种做法是哈希,我们使用字符串哈希记录下当前结点未配对的字符,若字符相同且两个点的字符相同,则可以组成合法的括号,直接二分得到配对即可。

代码:

#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int base=131;
char s[100005],ans[100005];
int len,p[100005],top;
ull h[100005],b[100005];
vector<pair<ull,int>> e[30];
void init(){
	for(int i=len;i>=1;i--){
		if(top&&s[p[top]]==s[i])top--;
        else{
            p[++top]=i;
            h[top]=h[top-1]*base+s[i];
        }
        b[i]=h[top];
        e[s[i]-97].push_back({b[i],i});
    }
    if(top){
    	cout<<-1;
    	exit(0);
	}
    for(int i=0;i<26;++i)sort(e[i].begin(),e[i].end());
}
int find(int p,ull v,int x){
	int l=0,r=e[p].size(),ans;
	while(l<=r){
		int mid=l+r>>1;
		if(e[p][mid].first<v)l=mid+1;
		else if(e[p][mid].first>v)r=mid-1;
		else{
			if(e[p][mid].second>x)r=mid-1;
			else{
				l=mid+1;
				ans=e[p][mid].second;
			}
		}
	}
	return ans;
}
void dfs(int l,int r){
	if(l>r)return;
	ans[l]='(',ans[r]=')';
	if(r-l==1)return;
	int x=find(s[l]-97,b[l+1],r);
    ans[x]=')';
    dfs(l+1,x-1),dfs(x+1,r);
}
signed main(){
	cin>>s+1;
	len=strlen(s+1);
	init();
	dfs(1,len);
	cout<<ans+1;
	return 0;
}

第二种做法,考虑动态规划,设置 \(dp_{i,j}\) 表示以 \(i\) 为最右端,在一个符号为 \(j\) 且在此之后到 \(i\) 可以组成一个合法括号串的最大位置。

我们可以得到方程式:

\[dp_{i,j}=dp_{dp_{i-1,s_i-97}-1,j} \]

看似复杂实际上很简单,相当于连接了两个括号串。

然后怎么处理答案呢?

我们从暴力方法可以通过搜索得到范围,那么我们知道了右端点位置,就可以用右端点找到一个最靠右的点与左端点进行匹配。

代码:

#include<bits/stdc++.h>
using namespace std;
int len,p[100005],cnt,dp[100005][30];
char s[100005],ans[100005];
bool check(){
	for(int i=1;i<=len;i++){
		if(cnt&&p[cnt]==s[i]-'a')cnt--;
		else p[++cnt]=s[i]-'a';
	}
	if(cnt)return true;
	cnt=0;
	return false;
}
void dfs(int l,int r){
	if(l>r)return ;
	int tmp=dp[r][s[l]-97];
	ans[l]='(',ans[tmp]=')';
	dfs(l+1,tmp-1),dfs(tmp+1,r);
}
signed main(){
	cin>>s+1;
	len=strlen(s+1);
	if(check()){
		cout<<-1;
		return 0;
	}
	for(int i=1;i<=len;i++){
	    for(int j=0;j<26;j++)if(dp[i-1][s[i]-97])dp[i][j]=dp[dp[i-1][s[i]-97]-1][j];
	    dp[i][s[i]-97]=i;
	}
	dfs(1,len);
	cout<<ans+1;
	return 0;
}
posted @ 2025-12-07 12:32  huhangqi  阅读(3)  评论(0)    收藏  举报