前言
【题目传送门】
并不是很难的一道题,但仍有值得学习之处。
题解
区间 DP 显然能看出。
而且对于配对的两个括号,肯定是要一起处理。
所以预处理每个括号配对的位置也能想到。
也能想到根据两个端点是否配对,分类讨论加法和乘法。
我遇到的问题
- 原本设计的 DP 没有只记录了左右括号分别涂某种颜色的情况,这样的话如果区间左右括号不配对就没办法转移了。
- 想写记搜,但是想到
solve
函数里面还要传入 (op),就不知道该返回什么了。(其实是我 sb 了,如果分情况返回值应该也是可以写的,只不过就略微冗杂了一点,这充分说明我很菜) - 分类讨论的时候还有个判断写错了。
正解的思路
- 记录 (dp(l,r,i,j)) 表示区间内左右端点不涂色/红色/蓝色的方案数。
- 貌似只能写搜索,因为要保证配对的括号在同一区间。
- 分情况转移两端是否配对的情况。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define FCC fclose(stdin),fclose(stdout)
const int INF = 0x3f3f3f3f,mod = 1e9+7,N = 706;
inline ll read()
{
ll ret=0;char ch=' ',c=getchar();
while(!(c>='0'&&c<='9')) ch=c,c=getchar();
while(c>='0'&&c<='9') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
return ch=='-'?-ret:ret;
}
ll dp[N][N][3][3];
int n,rit[N];
int stk[N],top;
char s[N];
inline ll Mod(ll x,ll y)
{
return ((x+y)%mod+mod)%mod;
}
void solve(int l,int r)
{
if(l+1==r)
{
dp[l][r][0][1]=dp[l][r][0][2]=dp[l][r][1][0]=dp[l][r][2][0]=1;
return;
}
if(rit[l]==r)
{
solve(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][r][0][1]+dp[l+1][r-1][i][j])%mod;
if(j!=2) dp[l][r][0][2]=(dp[l][r][0][2]+dp[l+1][r-1][i][j])%mod;
if(i!=1) dp[l][r][1][0]=(dp[l][r][1][0]+dp[l+1][r-1][i][j])%mod;
if(i!=2) dp[l][r][2][0]=(dp[l][r][2][0]+dp[l+1][r-1][i][j])%mod;
}
}
else
{
solve(l,rit[l]),solve(rit[l]+1,r);
for(int i=0;i<=2;i++)
for(int j=0;j<=2;j++)
for(int k=0;k<=2;k++)
for(int p=0;p<=2;p++)
{
// if((k==p&&k)||(i==k&&k)||(p==j&&p)) continue;
if(k==p&&p) continue;
dp[l][r][i][j]=Mod(dp[l][r][i][j],
dp[l][rit[l]][i][k]*dp[rit[l]+1][r][p][j]%mod);
}
}
}
int main()
{
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;i++)
{
if(s[i]=='(') stk[++top]=i;
else rit[stk[top--]]=i;
}
solve(1,n);
ll ans=0ll;
for(int i=0;i<=2;i++)
for(int j=0;j<=2;j++)
ans=(ans+dp[1][n][i][j])%mod;
printf("%lld
",ans);
return 0;
}