区间DP CF D题:Coloring Brackets

题目链接: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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值