Sweety

Practice makes perfect

导航


题目描述:

  给一个合法的括号串,然后问这串括号有多少种涂色方案,当然啦!涂色是有限制的。

  1,每个括号只有三种选择:涂红色,涂蓝色,不涂色。

  2,每对括号有且仅有其中一个被涂色。

  3,相邻的括号不能涂相同的颜色,但是相邻的括号可以同时不涂色。

问有多少种染色方式

这里分割子问题时候用匹配的位置分割,也就是搜索的分割点:

这里整理一下记忆化搜索的结题过程:(和DP有点像here

(1)分析最优子结构,分割子问题,这里子问题都是有特点的,比如串,可能就是和某个其他字符匹配,因为就可以从匹配的位置分割子问题
(2)确定dp状态的含义
(3)用递归的方式,也就是Top-down方式求解


分析:

(1)分析最优子结构  因为这道题目有一个括号匹配的限制,所以应该是针对于匹配括号进行分解的,所以分解后就出现了左右边界括号匹配和左右括号不匹配来两种情况,两个子问题解决之后再按照不同情况进行合并。
(2)确定dp状态的含义 因为相邻的两个括号和匹配的两个括号分别都有限制关系,并且颜色也很好几种(0无 1红 2蓝),所以需要记录首尾的颜色,顾四维度DP:dp[l][r][x][y] 表示区间 [l, r] 在左端点涂x色,右端点涂y色的情况下的方案数。其中0代表不涂色, 1代表涂红色, 2代表涂蓝色。

(3)递归求解,各个击破!!!

其实这道题里面的记忆化搜索有个特点,就是他记录的状态并不会出现多次使用,但是子问题还有很多的状态,所以单纯靠返回值(1个)没法记录,所以需要记忆化!!!

这道题用搜索更好理解一些(PS:其实多数题目只要状态确定了,记忆化搜索都比DP好理解一些,因为搜索是UP-Down的)

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
typedef long long ll;
const int mod = 1000000007;

char str[800];
int match[800];
ll dp[800][800][3][3];


void dfs(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(match[l]==r)///当前首尾匹配
    {
        dfs(l+1,r-1);
        for(int i=0; i<3; i++)  ///只能是有一个染色
            for(int j=0; j<3; 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  ///当前首尾不匹配
    {
        dfs(l,match[l]);
        dfs(match[l]+1,r);

        for(int i=0; i<3; i++)  ///第一个括号颜色
            for(int j=0; j<3; j++)   ///match[l]位置的颜色
                for(int k=0; k<3; k++)   ///match[l]+1位置的颜色
                   for(int p=0; p<3; p++)    ///r位置的颜色
                    {
                        if(j && j==k)continue;  ///j k是相邻的,颜色需要不同
                        dp[l][r][i][p]=(dp[l][r][i][p]+(dp[l][ match[l] ][i][j]*dp[ match[l]+1 ][r][k][p]%mod))%mod;
                    }
    }
}
int main()
{
    scanf("%s",str+1);

    int n=strlen(str+1);
    memset(match,0,sizeof match);

    int stk[800],top=-1;
    for(int i=1; i<=n; i++)
    {
        if(str[i]=='(') stk[++top]=i;
        else match[stk[top--]]=i;
    }

    memset(dp,0,sizeof dp);

    dfs(1,n);
    ll ans=0;
    for(int i=0; i<3; i++)
        for(int j=0; j<3; j++)
            ans=ans+dp[1][n][i][j];
    printf("%I64d\n",ans%mod);
    return 0;
}



dp问题的步骤
(1)分析最优子结构  因为这道题目有一个括号匹配的限制,所以应该是针对于匹配括号进行分解的,所以分解后就出现了左右边界括号匹配和左右括号不匹配来两种情况,两个子问题解决之后再按照不同情况进行合并。
(2)确定dp状态的含义 因为相邻的两个括号和匹配的两个括号分别都有限制关系,并且颜色也很好几种(0无 1红 2蓝),所以需要记录首尾的颜色,顾四维度DP:dp[l][r][x][y] 表示区间 [l, r] 在左端点涂x色,右端点涂y色的情况下的方案数。其中0代表不涂色, 1代表涂红色, 2代表涂蓝色。
(3)得到递推方程式

                          dp[l+1][r-1][x'][y'] (0<=x'<3, x'!=x, 0<=y'<3, y!=y')   (左右括号匹配 即括号套括号

dp[l][r][x][y] +=  

                         dp[l][r][x][y] += dp[l][nu][x'][y'] * dp[nu][r][x''][y'']      (nu是l对应的另一边括号)  (左右括号不匹配  即括号并列)

边界:当l+1 == r时:dp[l][r][0][1] = dp[l][r][1][0] = dp[l][r][0][2] = dp[l][r][2][0] = 1;

                             if(mapp[i]!=i+1)  dp[i][i+1][0][0]=dp[i][i+1][1][2]=dp[i][i+1][2][1]=1;

这道题还是很好的!!!!!!!!!
#include <bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
char s[710];
int n,mapp[710];
long long dp[710][710][3][3];
void getmap()   ///找出配对情况
{
    memset(mapp,0,sizeof(mapp));
    stack<int>q;
    while(!q.empty()) q.pop();
    for(int i=0; i<n; i++)
    {
        if(s[i]=='(') q.push(i);
        else
        {
            if(q.empty()) continue;
            int p=q.top();
            q.pop();
            mapp[p]=i;
            mapp[i]=p;
        }
    }
}
void DPfunc()
{
    while(~scanf("%s",s))
    {
        n=strlen(s);
        getmap();
        memset(dp,0,sizeof(dp));
        for(int i=0; i<n-1; i++)  ///对相邻的初始化
        {
            dp[i][i+1][0][1]=dp[i][i+1][0][2]=dp[i][i+1][1][0]=dp[i][i+1][2][0]=1;
            if(mapp[i]!=i+1)
                dp[i][i+1][0][0]=dp[i][i+1][1][2]=dp[i][i+1][2][1]=1;
        }
        for(int len=2; len<n; len++)  ///长度 想想为什么这个作为第一层循环而不是按照起始位置
                                      ///原因很简单,就是因为大的长度可能用到小的长度,
                                      ///而如果用起始位置作为循环因子,中间好多状态是没有求的
        {
            for(int i=0; i+len<n; i++) ///开始位置
            {
                int j=i+len;
                if(mapp[i]==j) ///i和j位置的括号是匹配的
                {
                    for(int p=0; p<=2; p++)
                    {
                        for(int q=0; q<=2; q++)
                        {
                            if(q!=1)
                                dp[i][j][0][1]=(dp[i][j][0][1]+dp[i+1][j-1][p][q])%mod;
                            if(p!=1)
                                dp[i][j][1][0]=(dp[i][j][1][0]+dp[i+1][j-1][p][q])%mod;
                            if(q!=2)
                                dp[i][j][0][2]=(dp[i][j][0][2]+dp[i+1][j-1][p][q])%mod;
                            if(p!=2)
                                dp[i][j][2][0]=(dp[i][j][2][0]+dp[i+1][j-1][p][q])%mod;
                        }
                    }
                }
                else   ///i和j位置的括号是不匹配的
                {
                    int u=mapp[i];
                    for(int p=0; p<=2; p++)
                        for(int q=0; q<=2; q++)
                            for(int x=0; x<=2; x++)
                                for(int y=0; y<=2; y++)
                                    if(!((x==1&&y==1)||(x==2&&y==2)))
                                        dp[i][j][p][q]=(dp[i][j][p][q]+(dp[i][u][p][x]*dp[u+1][j][y][q])%mod)%mod;
                }
            }
        }



        long long ans=0;
        for(int i=0; i<=2; i++)
            for(int j=0; j<=2; j++)
                ans=(ans+dp[0][n-1][i][j])%mod;
        printf("%lld\n",ans);
    }
}
int main()
{
    //freopen("in.txt","r",stdin);
    DPfunc();
    return 0;
}