Description
给定一个配对的括号序列,求染色方案。染色规则如下:
- 一个括号可以染红色、蓝色或不染色
- 一对匹配的括号需要且只能将其中一个染色
- 相邻两个括号颜色不能相同(但可以都不染色)
Solution
一道区间 dp 的好题。
括号配对
这个,不用多说了吧。
令 right[i]
为第 个括号的配对括号。
代码很好写:
scanf("%s",s+1);
int n=strlen(s+1);
for(int i=1;i<=n;i++){
if(s[i]=='(')stk.push(i);
else{
right[stk.top()]=i;
right[i]=stk.top();
stk.pop();
}
}
具体原理很简答,可以看 UVA673 平衡的括号 Parentheses Balance。
设计状态
看到题后第一反应当然是令 dp[l][r]
为将区间 内染色的总方案数。
但是题目上说:
- 一个括号可以染红色、蓝色或不染色
- 一对匹配的括号需要且只能将其中一个染色
- 相邻两个括号颜色不能相同(但可以都不染色)
有了这些限制之后,我们发现,如果单纯地定义 dp[l][r]
为将区间 内染色的总方案数,很难转移。
换句话说,我们还需要更多的信息来进行转移。
其实只需要增加这个区间两端的括号颜色,就可以进行转移了。
那么就设计 dp[l][r][p][q]
为将区间 内染色的总方案数,且最左边的括号颜色为 ,最右边的括号颜色为 。
其中 的值为 时代表不染色,为 时代表蓝色,为 时代表红色。
转移方程
首先是初始条件:如果 ,那么 一定是一对匹配的括号。
所以有 dp[l][r][0][1]=dp[l][r][0][2]=dp[l][r][1][0]=dp[l][r][2][0]=1;
。
毕竟整个区间长度就是 ,那么一旦左、右端点的颜色确定了,整个区间的染色方案自然也就确定了,所以只有一种。
由于「一对匹配的括号需要且只能将其中一个染色」,所以 dp[l][r][0][0]=dp[l][r][1][2]=dp[l][r][2][1]=dp[l][r][1][1]=dp[l][r][2][2]=0
,不必再专门这五种专门赋值了。
然后,转移的时候,需要分类讨论:
- 如果第 个括号刚好和第 个括号配对:
那么这个区间类似于这样:(......)
。
所以易知 dp[l][r][0][0]=dp[l][r][1][2]=dp[l][r][2][1]=dp[l][r][1][1]=dp[l][r][2][2]=0
。原因和上面一样。
所以这五个就不需要再转移了。
至于剩下的四种情况,以 dp[l][r][0][1]
为例:
由于左端点不染色,右端点是
所以它不能 dp[l+1][r-1][·][1]
转移过来。
否则相邻两个括号的颜色相同,就不符合条件了。
那么我们从 到 枚举 ,然后看 的值来一个一个转移即可。
for(int i=0;i<=2;i++){
for(int j=0;j<=2;j++){
if(j!=1)dp[l][r][0][1]+=dp[l+1][r-1][i][j],dp[l][r][0][1]%=mod;
if(j!=2)dp[l][r][0][2]+=dp[l+1][r-1][i][j],dp[l][r][0][2]%=mod;
if(i!=1)dp[l][r][1][0]+=dp[l+1][r-1][i][j],dp[l][r][1][0]%=mod;
if(i!=2)dp[l][r][2][0]+=dp[l+1][r-1][i][j],dp[l][r][2][0]%=mod;
}
}
- 如果第 个括号刚好和第 个括号不配对:
那么我们找出与第 个括号配对的括号。
区间大概是这样的:(......)...(
那么我们分别处理出区间 的方案数和区间 的方案数,然后相乘即可。
需要注意的是, 与 处的括号颜色不能相同。这种情况需要特判。
for(int i=0;i<=2;i++){
for(int j=0;j<=2;j++){
for(int p=0;p<=2;p++){
for(int q=0;q<=2;q++){
if((j==1&&p==1)||(j==2&&p==2))continue;//right[l] 与 right[l]+1 处的括号颜色相同
dp[l][r][i][q]+=(dp[l][right[l]][i][j]*dp[right[l]+1][r][p][q]%mod);
dp[l][r][i][q]%=mod;
}
}
}
}
那么,转移方式就弄好啦!
有一个需要注意的点就是:转移顺序最好是以记忆化搜索的顺序来,不然的话会很麻烦。
最后,上代码:
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cstdlib>
#include<stack>
#define MAXN 800
#define int long long
using namespace std;
char s[MAXN];
int dp[MAXN][MAXN][5][5],right[MAXN];
stack<int>stk;
const int mod=1000000007;
void DFS(int l,int r){
if(r==l+1){
dp[l][r][0][1]=dp[l][r][0][2]=dp[l][r][1][0]=dp[l][r][2][0]=1;
}
else if(right[l]==r){//l,r 配对
DFS(l+1,r-1);//先处理出来 [l+1,r-1] 的方案数
for(int i=0;i<=2;i++){
for(int j=0;j<=2;j++){
if(j!=1)dp[l][r][0][1]+=dp[l+1][r-1][i][j],dp[l][r][0][1]%=mod;
if(j!=2)dp[l][r][0][2]+=dp[l+1][r-1][i][j],dp[l][r][0][2]%=mod;
if(i!=1)dp[l][r][1][0]+=dp[l+1][r-1][i][j],dp[l][r][1][0]%=mod;
if(i!=2)dp[l][r][2][0]+=dp[l+1][r-1][i][j],dp[l][r][2][0]%=mod;
}
}
//刚才说过的转移方程
}
else{
DFS(l,right[l]);
DFS(right[l]+1,r);
//先处理区间 [l,right[l]] 和区间 [right[l]+1,r]。
for(int i=0;i<=2;i++){
for(int j=0;j<=2;j++){
for(int p=0;p<=2;p++){
for(int q=0;q<=2;q++){
if((j==1&&p==1)||(j==2&&p==2))continue;
dp[l][r][i][q]+=(dp[l][right[l]][i][j]*dp[right[l]+1][r][p][q]%mod);
dp[l][r][i][q]%=mod;
}
}
}
}
}
}
signed main(void){
scanf("%s",s+1);
int n=strlen(s+1);
for(int i=1;i<=n;i++){
if(s[i]=='(')stk.push(i);
else{
right[stk.top()]=i;
right[i]=stk.top();
stk.pop();
}
}
//处理出配对的括号
DFS(1,n);
int ans=0;
for(int i=0;i<=2;i++){
for(int j=0;j<=2;j++){
ans+=dp[1][n][i][j];
ans%=mod;
}
}
printf("%lld\n",ans);
return 0;
}