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;
}

浙公网安备 33010602011771号