[HDU 5184][BestCoder #32]Brackets(卡特兰数+组合数学+乘法逆元)

题目链接

http://acm.hdu.edu.cn/showproblem.php?pid=5184

思路

这道题看上去是个搜索或者DP的题目,但是实际上题目只需要 O(1) 时间便能求出答案,这道题是由一道很经典的卡特兰数的问题改编而来。原题是这样的:

n+m 个人排队买票,并且满足,票价为50元,其中 n 个人各手持一张50元钞票,m个人各手持一张100元钞票,除此之外大家身上没有任何其他的钱币,并且初始时候售票窗口没有钱,问有多少种排队的情况数能够让大家都买到票。

上题相当于限制了所有拿50元的人必须和拿100的人达到两两配对。可能还会多出一部分拿100元的人。最终其答案为 (n+mn)(n+mn+1)

而在这个题中,由于已经有一部分括号已经被确定了,那么这部分括号我们无视掉,此时还剩下一些左括号和右括号,左括号相当于拿50元的人,右括号相当于拿100元的人,而且可能拿100的人比拿50的人多(右括号个数保证大于等于左括号)。那么这个题就和上面那道题一样了。

然后注意要判定无解的情况

代码

1、扩展欧几里得在线求逆元

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 1100000
#define MOD 1000000007

using namespace std;

typedef long long int LL;

LL facto[MAXN]; //facto[i]=i!
char s[MAXN];

inline LL extGCD(LL a,LL b,LL &x,LL &y)
{
    if(!b)
    {
        x=1;
        y=0;
        return a;
    }
    LL gcd=extGCD(b,a%b,x,y);
    LL t=x;
    x=y;
    y=t-(a/b)*y;
    return gcd;
}

inline LL rev(LL t) //求t的逆元
{
    LL x,y;
    extGCD(t,MOD,x,y);
    x=(x%MOD+MOD)%MOD;
    return x;
}

inline LL C(int a,int b) //C(a,b)
{
    return (facto[a]*rev(facto[b]*facto[a-b]%MOD))%MOD;
}

int main()
{
    facto[0]=facto[1]=1;
    for(int i=2;i<MAXN;i++) facto[i]=(facto[i-1]*i)%MOD;
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        bool flag=true;
        //memset(s,0,sizeof(s));
        int sum=0; //sum=必须匹配的右括号长度
        scanf("%s",s+1);
        int m=strlen(s+1); //已经确定的括号长度
        for(int i=1;i<=m;i++)
        {
            if(s[i]=='(') sum++;
            else sum--;
            if(sum<0) flag=false;
        }
        if(!flag||n&1) //无解
        {
            printf("0\n");
            continue;
        }
        printf("%lld\n",((C(n-m,(n-m-sum)/2)-C(n-m,(n-m-sum)/2-1)+MOD)%MOD));//%MOD+MOD)%MOD);
    }
    return 0;
}

2、线性筛法离线求逆元

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 1100000
#define MOD 1000000007

using namespace std;

typedef long long int LL;

LL facto[MAXN],frac[MAXN]; //facto[i]=i!,frac[i]=1/(i!)
char s[MAXN];
LL rev[MAXN];

inline LL C(int a,int b) //C(a,b)
{
    return (((facto[a]*frac[b])%MOD)*frac[a-b])%MOD;
}

int main()
{
    rev[1]=1;
    for(int i=2;i<MAXN;i++)
    {
        rev[i]=(MOD-MOD/i)*rev[MOD%i]%MOD;
    }
    facto[0]=facto[1]=frac[0]=frac[1]=1;
    for(int i=2;i<MAXN;i++) facto[i]=(facto[i-1]*i)%MOD;
    for(int i=2;i<MAXN;i++) frac[i]=(frac[i-1]*rev[i])%MOD;
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        bool flag=true;
        //memset(s,0,sizeof(s));
        int sum=0; //sum=必须匹配的右括号长度
        scanf("%s",s+1);
        int m=strlen(s+1); //已经确定的括号长度
        for(int i=1;i<=m;i++)
        {
            if(s[i]=='(') sum++;
            else sum--;
            if(sum<0) flag=false;
        }
        if(!flag||n&1) //无解
        {
            printf("0\n");
            continue;
        }
        printf("%lld\n",((C(n-m,(n-m-sum)/2)-C(n-m,(n-m-sum)/2-1)+MOD)%MOD));//%MOD+MOD)%MOD);
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值