思想:dp的定义为dp[l][r][lc][rc],表示此区间一定为正确的括号匹配,从l到r区间,l位置上色为lc,r位置上色为rc的上色总方案数。
这道题与传统的区间DP不同,传统的区间DP枚举的是区间的长度,找断点。而对于这道题来说,需要找的是符合一个正确括号匹配的小区间来得到大区间的值,要排除不符合要求的小区间情况。通过观察得知,区间的合并不可能是相交的,只可能是包含与并列的关系。
这个博客的图画的非常好:Codeforces149 D. Coloring Brackets(区间dp,合法括号序列性质)_live4m的博客-优快云博客
此外,这道题的区间定义与传统不同,多定义了2个状态。通过上述分析,我们大致能够知道一个括号一定有唯一匹配,于是可以用栈找到相应的匹配。
DP的转移方程,首先,如果l,r相互匹配,那么就分析l+1,r-1的区间上色情况。
其次,如果不匹配,那么l的匹配点一定在l到r区间内,因为没有交叉的情况发生。
还应注意数据要用long long,情况一定要分析清楚。
这道题给人启发,让我复习了递归的DP写法,是一道非常好的题。
#include<bits/stdc++.h>
#include<cstring>
#include<string>
#include<iostream>
using namespace std;
const int volume=710;
const long long mod=1e9+7;
long long dp[volume][volume][3][3];
stack<int> stk;
int match[volume];
int dfs(int l,int r,int lc,int rc)
{
//cout<<l<<' '<<r<<' '<<lc<<' '<<rc<<endl;
if(dp[l][r][lc][rc]!=0)
return dp[l][r][lc][rc];
if(l+1==r)
{
dp[l][r][lc][rc]=1;
return 1;
}
if(match[l]==r)
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
{
if(i>0&&i==lc||j>0&&j==rc)continue;///相邻上色的不能颜色相同
if(match[l+1]==r-1)
{
if(!i&&!j)continue;
if(i>0&&j>0)continue;///匹配的恰有一个上色
dp[l][r][lc][rc]+=dfs(l+1,r-1,i,j);
dp[l][r][lc][rc]%=mod;
}
else
{
dp[l][r][lc][rc]+=dfs(l+1,r-1,i,j);
dp[l][r][lc][rc]%=mod;
}
}
else
{
int k=match[l];
for(int x=0;x<3;x++)
for(int y=0;y<3;y++)
{
if(!lc&&!x)continue;
if(lc>0&&x>0)continue;
if(x>0&&x==y)continue;///合并点相邻的不能上相同颜色
if(match[k+1]==r)
{
if(!rc&&!y)continue;
if(rc>0&&y>0)continue;
}
dp[l][r][lc][rc]+=dfs(l,k,lc,x)%mod*dfs(k+1,r,y,rc)%mod;
dp[l][r][lc][rc]%=mod;
}
}
return dp[l][r][lc][rc];
}
int main()
{
///开启加速后,就不能cin,cout,scanf等混用
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
memset(dp,0,sizeof(dp));
string s;
cin>>s;
int n=s.length();
for(int i=0;i<n;i++)
{
if(s[i]=='(') stk.push(i);
else
match[stk.top()]=i,stk.pop();
}
int ans=0;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
{
if(match[0]==n-1)
{
if(!i&&!j)continue;
if(i>0&&j>0)continue;
}
ans+=dfs(0,n-1,i,j);
ans%=mod;
}
ans%=mod;
cout<<ans<<endl;
return 0;
}