括号序列 解题报告
括号序列
题目背景
你不努力,未来别人腿咚的墙还是你砌的!
题目描述
课堂上,Felix刚刚学习了关于括号序列的知识。括号序列是一个只由左括号"("和右括号")"构成的序列:进一步的,一个合法的括号序列是指左括号和右括号能够一一匹配的序列。
如果用规范的语言说明,一个合法的括号序列可以有以下三种形式:
1 s=""(空串),s是一个合法的括号序列
2 s=XY,其中X,Y均为合法的括号序列,则S也是一个合法的括号序列;
3 s=(X),其中X为合法的括号序列,则S也是一个合法的括号序列。
这时老师在黑板上写出了一个括号序列:"()))()"
Felix一眼就看出这个序列并不是合法的括号序列。
这时老师提出了一个这样的问题:能够在序列中找出连续的一段,把这一段里面的左括号变成右括号,右括号变成左括号,变换之后整个序列可以变成合法的呢?
Felix想到,可以把[3..5]进行调换,这样序列就会变为()(()),是一个合法的序列。很明显,不止有一种方法可以使整个序列合法。
这时,老师又在黑板上写出了一个长度为N的括号序列。Felix想,能否对这个序列进行至多一次变换,使他变合法呢?
输入输出格式
输入格式:
第一行一个整数T,代表数据的组数;接下来T行,每一行一组数据。
每组数据一行,代表给出的括号序列。
输出格式:
输出共T行,对于每组数据,输出“possible”(可以变换)或者“impossible”(不可变换)。(不含引号)
说明
对于50%的数据,T<=5,N<=20;
对于100%的数据,T<=10,N<=5000。
一开始想把已有的匹配括号全部缩掉直接贪心,搞了一会儿发现是错的。
无奈,打了个暴力,枚举翻转区间并检查是否合法,居然还错了。。
缺乏想到正解的原因是,我只知道拿栈来判断括号匹配,殊不知有实用性更好的方法。
如果把括号抽象为一个数列,左括号为1,右括号为-1。
对S的某个前缀p,如果数列的前缀和\(f[p]\)的值大于0,则说明左括号数量不比右括号少。
对于这个前缀p,如果每个\(f\)数组值大于等于0且\(f[p]=0\),则这个前缀p是匹配的。
由此引入这个题的正解dp
令\(dp[i][j][k]\)表示前缀\(i\)前缀和为\(j\)时处于\(k\)状态(0为翻转区间左边,1为翻转区间,2为翻转区间右边),进行可达性统计
初始:\(dp[0][0][0]=1\)
目标:\(dp[n][0][0/1/2]\)
可以滚动一下优化空间,另外这个题居然有点卡常。。
#include <cstdio>
#include <cstring>
int max(int x,int y){return x>y?x:y;}
const int N=5010;
int f[N],t,dp[2][N][3];
char c[N];
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%s",c);
memset(f,0,sizeof(f));
memset(dp,0,sizeof(dp));
int n=strlen(c);
for(int i=0;i<n;i++)
f[i+1]=c[i]=='('?1:-1;
dp[0][0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=i;j++)
for(int k=0;k<=2;k++)
dp[i&1][j][k]=0;
int t=f[i];
for(int j=0;j<=i;j++)
{
if(j>=t)
{
dp[i&1][j][0]=dp[i-1&1][j-t][0];
dp[i&1][j][2]=max(dp[i-1&1][j-t][1],dp[i-1&1][j-t][2]);
}
}
t=-t;
for(int j=0;j<=i;j++)
if(j>=t)
dp[i&1][j][1]=max(dp[i-1&1][j-t][0],dp[i-1&1][j-t][1]);
}
if(dp[n&1][0][0]||dp[n&1][0][1]||dp[n&1][0][2])
printf("possible\n");
else
printf("impossible\n");
}
return 0;
}
2018.6.21