CodeForces - 629C dp 合法括号方案数枚举

题目链接:http://codeforces.com/problemset/problem/629/C

 

题意:

          给你一个长度为m的括号待匹配串,让你在这个串的前面加p串和后面加上q串,使得这个括号串合法(合法的意思是:1.你的左括号数量必须等于右括号数量;2.对于任意前缀,左括号的数量必须大于等于右括号数量,即每个右括号都有对应的匹配的最近的左括号)。

 

做法:

       题目中有一个关键的数据信息n-m<=2000,这就说明,我们即将要构造出的这个q串和p串的长度和不会超过2000,那么就好办了,你会发现,只要我们知道了前面的p串中左括号的数量,那么后面的q串中的数量我们也能知道,那么dp的两个维度就会是前面i个字符中左括号的数量,但是我们会发现这样转移有点麻烦,所以我们就可以让这个j变成,左括号比右括号多j个,因为q串中左括号的数量必须大于等于右括号。这样的话转移方程就会很好写了,dp[i][j]=(dp[i-1][j-1]+dp[i-1][j+1]),为什么呢,因为我们长度为i的时候的值一定是从长度为i-1来的,只会在后面加上一个左括号或者右括号,所以就是在j的基础上+1或者-1,那么就能很明显的得到转移方程啦。

        在枚举时候的控制,我写在注释里了,但是发现网上有更好的,就是p+s串合法,即p中多出的左括号加上s中的多出的右括号要大于等于0,另一个就是前面多出的左括号的和必须要比后面你能加上的数量要少。

 

#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
const int mod=(int)1e9+7;
typedef long long ll;
ll dp[maxn][maxn],ans;
int n,m,scl,sop,minn;
char s[100005];
int main(){
    dp[1][1]=dp[0][0]=1;
    for(int i=2;i<=2000;i++){
        for(int j=0;j<=i;j++){
           if(j>0) dp[i][j]+=dp[i-1][j-1];
           dp[i][j]=(dp[i][j]+dp[i-1][j+1])%mod;
        }
    }
    scanf("%d%d%s",&n,&m,s+1);
    for(int i=1;i<=m;i++){
        if(s[i]=='(') sop++;
        else scl++;
        minn=min(minn,sop-scl);
    }
    minn=-minn;
    int ri_le_cl=sop-scl;
    if(sop>n/2||scl>n/2){
        printf("0\n");
        return 0;
    }
    if(m==n){
        if(ri_le_cl!=0||minn!=0) printf("0\n");
        else printf("1\n");
        return 0;
    }
    for(int i=0;i<=n-m;i++){
        for(int j=minn;j<=i;j++){
            ///在这里我的minn就控制了前面必须要多于s中多出的右括号
            if((i-j)&1) continue;
            int pa=(i-j)/2;
            int preop=pa+j,precl=pa,latop=n/2-sop-preop,latcl=n/2-pa-scl;
            if(latop<0||latcl<0||latcl<ri_le_cl||latop>latcl) continue;
            ///这里我要求后面的右括号数量必须要大于等于中间多出的左括号 
            //如果后面的q串中有一个小于0则不合法,同时,如果右边的串中
            //右括号数量小于至少需要的量或者右括号小于左括号的量,也是不合法的
            int sum_la=n-m-i;
            ans=(ans+dp[i][j]*dp[sum_la][latcl-latop]%mod)%mod;
        }
    }
    printf("%lld\n",ans);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值