HDU - 5184 Brackets 解题报告(数论)(卡特兰数)(逆元)

题目描述

题目:https://vjudge.net/problem/HDU-5184
大概意思:告诉你目标的字符串长度和开头的几个括号,求可以以这几个括号开头的合法括号字符串的数量。

思路分析

在这个字符串构建的过程中,需要时刻保持:左括号数大于右括号数。如果它没有给开头序列,那么,这就是卡特兰数的模型I(不打清楚的可以先下hdu 2067作为铺垫)。但是,如果给了开头序列,我们的问题就多了一点变化。通过上一道题(hdu2067),我们知道:若n==m,那么保持目前0(表示向右移动)的数量要大于1(表示向上移动)的数量。但是,这道题由于给了初始序列,所以,不能按照之前的思路直接套了,需要一点思考。
回顾一下:在卡特兰数的0和1排序问题中,关于(2n n)-(2n (n-1))的证明是这样的:
找到第一个0的个数超过1的个数的位置,然后令前面的1变为0,0变为1,则就出了一个序列又n+1个1和n-1个0,那么,有n+1个1和n-1个0就是方案不合理的充要条件,这种情况下的不合理安排方案数为(2n n+1),原公式的证。
那么,这道题我们的思路需要一点变化:
因为给了初始序列,那么,我们设剩下所需的左括号数和右括号数分别为l和r,那么,当r<l时候,方案即为不合理。这个时候,我们需要的,是找到第一个产生不合理的位置,然后,将后面剩下的左右括号数对调,就能得到一个有r+1个右括号,l-1个左括号的序列,那么,模仿上面的做法,不合理的方案数就是(l+r,r+1)。
也可以这么想:我们从右边向左看这个序列,我们需要保证右括号数大于左括号数。若出现不合理,则找到第一个不合理的点,包括这个点在内到序列最右边的元素左右括号对调,也能得到相同的结论。
除此之外,这里还涉及到两个点:快速幂和逆元。
快速幂套模板:

ll quick_pow(ll a,ll b)
{
	ll result=1;
	while(b)
	{
		if(b&1) result=a*result%mod;
		a=a*a%mod;
		b>>=1; 
	}
	return result;
}

由于计算组合数的过程中有除法,因此,我们需要利用逆元来防止误差。逆元的值,根据费马小定理,是组合数分母部分的mod-2次方。
最后输出的时候一定要注意要+mod再%mod,因为两个组合数的差可能是负数。

完整代码

#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
const ll maxn=1000005;
ll f[maxn]={1,1};
void make_list()
{
	for(int i=2;i<maxn;i++)
	{
		f[i]=f[i-1]*i%mod;
	}
}
ll quick_pow(ll a,ll b)
{
	ll result=1;
	while(b)
	{
		if(b&1) result=a*result%mod;
		a=a*a%mod;
		b>>=1; 
	}
	return result;
}
ll c(ll a,ll b)
{
	ll t=f[a-b]*f[b]%mod;
	ll inv=quick_pow(t,mod-2);
	return f[a]*inv%mod;
}

int main()
{
	make_list();
	ll n;
	while(~scanf("%lld",&n))
	{
		char str[maxn];
		scanf("%s",str);
		if(n%2)
		{
			cout<<"0"<<endl;
			continue;
		}
		int l=0,r=0;
		int flag=0;
		int len=strlen(str);
		for(int i=0;i<len;i++)
		{
			if(str[i]=='(') l++;
			if(str[i]==')') r++;
			if(r>l)
			{
				flag=1;
				break;
			}
		}
		if(flag==1||l<r)
		{
			cout<<"0"<<endl;
			continue;
		}
		l=n/2-l;
		r=n/2-r;
		if(l<0||r<0)
		{
			cout<<"0"<<endl;
			continue;
		}
		printf("%lld\n",(c(l+r,r)-c(l+r,r+1)+mod)%mod);
		
	}
	return 0;
 }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值