一道难度适中的区间dp,用记忆化搜索比较好处理。
将字符串涂色的状态定义成动态规划的状态,容易推出需要记录区间的左右端点(是个区间dp基本都得这样做),由于题目限制左右端点的涂色,所以要记录左右端点信息,才知道下一步如何递推。
所以,得出结论:
用dp[a][b][c][d] 四位数组,分别表示字符串左区间、字符串右区间、左端点颜色、右端点颜色
之后就是转移过程,见代码详尽注释。
#include <iostream>
#include <string.h>
#include <stack>
#include <algorithm>
#include <string>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
int n;
string s;
stack <int> st;
int m[720];
ll dp[720][720][3][3];
ll dfs(int a, int b, int c, int d)
{
//a表示左端点位置 b表示右端点位置 c表示左端点颜色 d表示右端点颜色
//0表示没涂色 1表示涂了一种颜色 2表示涂了另一种颜色
ll res = 0;
if (b <= a || dp[a][b][c][d] != -1)//如果区间已经被分的不能再分,或者上次已经算过,返回
{
if (b <= a)//只有一种方案
return 1;
else
return dp[a][b][c][d];
}
if (b == m[a])//遇到这个区间左右端点恰好匹配 那么左右端点的方案已经固定了 向内部推一层
{
if (c != 1) res += dfs(a + 1, b - 1, 1, 0) % mod;
//如果左端点颜色不为1,则内部相邻的左端点颜色可以为1,左端点涂了色,右端点一定不能涂色
if (d != 1) res += dfs(a + 1, b - 1, 0, 1) % mod;
//如果右端点颜色不为1,则同理
if (c != 2) res += dfs(a + 1, b - 1, 2, 0) % mod;
//如果左端点颜色不为2,则同理
if (d != 2) res += dfs(a + 1, b - 1, 0, 2) % mod;
//如果右端点颜色不为2,则同理
}
else
{
//这个区间左右端点不匹配,则有四种情况
res += (dfs(a + 1, m[a] - 1, 0, 1) * dfs(m[a] + 1, b, 1, d)) % mod;
res += (dfs(a + 1, m[a] - 1, 0, 2) * dfs(m[a] + 1, b, 2, d)) % mod;
ll p = dfs(m[a] + 1, b, 0, d);
if (c != 1) res += (p * dfs(a + 1, m[a] - 1, 1, 0)) % mod;
if (c != 2) res += (p * dfs(a + 1, m[a] - 1, 2, 0)) % mod;
}
dp[a][b][c][d] = res % mod;//记录已经算好的
return res % mod;
}
int main() {
while (getline(cin, s))
{
n = s.size();
for (int i = 0; i < n; i++)//用m数组记录匹配括号的信息
{
if (s[i] == '(') st.push(i);
else
{
m[st.top()] = i;
st.pop();
}
}
memset(dp, -1, sizeof(dp));//进行记忆化搜索之前,将dp数组置为-1,避免有0的方案出现却未被计算
printf("%lld\n", dfs(0, n - 1, 3, 3));
}
return 0;
}