题目链接:Problem - D - Codeforces
题目要求:
1.括号要么红色,要么蓝色,要么不涂颜色
2.配对的括号 有且只有一个且必须得有一个涂 颜色。
3.相邻两个括号不能有相同的颜色,但是可以都不涂颜色
求方案数。
分析:
DP分析方式:
(1)分析最优子结构,分割子问题,这里子问题都是有特点的,比如串,可能就是和某个其他字符匹配,因为就可以从匹配的位置分割子问题
(2)确定dp状态的含义此题主要是存在两个限制情况,两个配对的括号有且只有一个且必须得有一个括号涂颜色。
并且相邻两个括号不能有相同的颜色,但是可以都不涂颜色。
所以此区间DP的题目主要是按照匹配情况来缩小区间的。
DP状态表示:DP【i】【j】【k】【t】: i~j这个区间的i这个点的颜色为k 和j这个点的颜色为t
而i~j这个区间内部的其他点的颜色情况无需在意,因为dp[i + 1][j - 1][][]中已经涵盖其他所有情况。
其中会分为
情况1: i ~ j恰好匹配。dp[i][j][k][t] += dp[i + 1][j - 1][][];
为什么按照i~j恰好匹配,只需要 i + 1 , j - 1呢?
因为题目要求了配对的括号,有且只有一个可以染色且必须染色。需要特别处理。
所以
dp[i][j][1][0] += dp[i + 1][j - 1][0 / 2][0/1/2] , 如果i涂了颜色1,那么i + 1不能相同颜色,所以 0 / 2
dp[i][j][2][0] += dp[i + 1][j - 1][0 / 1][0/1/2]
dp[i][j][0][1],dp[i][j][0][2]。
情况2: 如果i ~ j 不匹配。
那么就可以分为 左半区间 i ~ match[i] , 其中match[i]为与i配对的括号。和右半区间 match[i] + 1 ~ j
dp[i][j][][] += dp[i][match[i]][0/1/2][0/1/2]
dfs代码实现:
# include <iostream>
# include <cstring>
# include <stack>
using namespace std;
const int N = 710 , mod = 1e9 + 7;
long long dp[N][N][3][3]; // dp[i][j][k][l]的含义是 i ~ j 的区间,两端的 i这个位置被染成了k这个颜色,j这个位置被染成了l这个颜色
// 其中0为不染色,1为红色,2为蓝色
char ch[N];
stack<int> st;
int match[N]; //左括号与右括号匹配时,右括号的位置。因为此次DP我们按照左括号的匹配情况进行拆分求解DP
void dfs(int l , int r)
{
if(l + 1 == r) // 最后的最小子情况匹配的情况
{
dp[l][r][1][0] = dp[l][r][2][0] = dp[l][r][0][1] = dp[l][r][0][2] = 1;
return;
}
else
{
if(match[l] == r) //如果L和R恰好匹配的话,那么缩小就是两边一个一个的缩小
{
// 为什么是两边一起缩小呢?区间DP不都是左边缩小或者右边缩小嘛?这是因为这道题目对匹配的情况具有条件限制,两个必须得进行染色操作,并且只能有一个被染色
dfs(l + 1 , r - 1); // 通过递归,先将小区间的dp值处理完
for(int i = 0 ; i <= 2 ; i++)
{
for(int j = 0 ; j <= 2 ; j++)
{
// 有且只有一个涂颜色,所以一个为0,一个为1
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 // 如果L和R不匹配的话
{
dfs(l,match[l]);
dfs(match[l] + 1 , r);
for(int i = 0 ; i <= 2 ; i++) // L对应的颜色
{
for(int j = 0 ; j <= 2 ; j++) // match[l]对应的颜色
{
for(int k = 0 ; k <= 2 ; k++) //match[l] + 1对应的颜色
{
for(int z = 0 ; z <= 2 ; z++) // r对应的颜色
{
if(j && j == k) // 如果j涂了颜色,并两个相邻节点之间涂同一个颜色,不满足条件,直接continue;
{
continue;
}
dp[l][r][i][z] = ( dp[l][r][i][z] + dp[l][ match[l] ][i][j] * dp[match[l] + 1][r][k][z] ) % mod;
}
}
}
}
}
}
}
int main()
{
cin >> ch + 1;
int len = strlen(ch + 1); // 字符长度
for(int i = 1 ; ch[i] ; i++)
{
if(ch[i] == '(')
{
st.push(i);
}
else
{
int temp = st.top();
st.pop();
match[temp] = i; // 右括号与左括号进行了匹配
}
}
dfs(1,len);
long long ans = 0;
for(int i = 0 ; i <= 2 ; i++)
{
for(int j = 0 ; j <= 2 ; j++)
{
ans = ( ans + dp[1][len][i][j] ) % mod;
}
}
printf("%lld\n",ans);
return 0;
}
循环代码实现:
# include <iostream>
# include <cstring>
# include <stack>
using namespace std;
const int N = 710 , mod = 1e9 + 7;
long long dp[N][N][3][3]; // dp[i][j][k][l]的含义是 i ~ j 的区间,两端的 i这个位置被染成了k这个颜色,j这个位置被染成了l这个颜色
// 其中0为不染色,1为红色,2为蓝色
char ch[N];
stack<int> st;
int match[N]; //左括号与右括号匹配时,右括号的位置。因为此次DP我们按照左括号的匹配情况进行拆分求解DP
int main()
{
cin >> ch + 1;
int len = strlen(ch + 1); // 字符长度
for(int i = 1 ; ch[i] ; i++)
{
if(ch[i] == '(')
{
st.push(i);
}
else
{
int temp = st.top();
st.pop();
match[temp] = i; // 右括号与左括号进行了匹配
}
}
for(int tt = 1 ; tt <= len ; tt++)
{
for(int l = 1 ; l + tt <= len ; l++)
{
int r = l + tt;
if(tt == 1) // 两个的情况
{
if(r == match[l]) // 如果两个配对则满足条件
{
dp[l][r][1][0] = dp[l][r][2][0] = dp[l][r][0][1] = dp[l][r][0][2] = 1;
}
// 不匹配则不满足条件为0
}
else
{
if(match[l] == r) //如果L和R恰好匹配的话,那么缩小就是两边一个一个的缩小
{
for(int i = 0 ; i <= 2 ; i++)
{
for(int j = 0 ; j <= 2 ; j++)
{
// 有且只有一个涂颜色,所以一个为0,一个为1
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
{
for(int i = 0 ; i <= 2 ; i++) // L对应的颜色
{
for(int j = 0 ; j <= 2 ; j++) // match[l]对应的颜色
{
for(int k = 0 ; k <= 2 ; k++) //match[l] + 1对应的颜色
{
for(int z = 0 ; z <= 2 ; z++) // r对应的颜色
{
if(j && j == k) // 如果j涂了颜色,并两个相邻节点之间涂同一个颜色,不满足条件,直接continue;
{
continue;
}
dp[l][r][i][z] = ( dp[l][r][i][z] + dp[l][ match[l] ][i][j] * dp[match[l] + 1][r][k][z] ) % mod;
}
}
}
}
}
}
}
}
long long ans = 0;
for(int i = 0 ; i <= 2 ; i++)
{
for(int j = 0 ; j <= 2 ; j++)
{
ans = ( ans + dp[1][len][i][j] ) % mod;
}
}
printf("%lld\n",ans);
return 0;
}