生成字符串(卡特兰数应用)

luogu P1641 生成字符串

Description

lxhgww 需要把 n n n 个1 和 m m m 个0组成字符串,同时保证在任意的前 k k k 个字符中,1的个数不能少于0的个数。现在 lxhgww 想要知道满足要求的字符串共有多少个。

Solution

根据题意,字符串的长度为 n + m n+m n+m,如果将问题退化成只求要求有 n n n 个1的方案,答案很明显就是 C n + m n \mathrm{C}_{n+m}^n Cn+mn,但如果要保证在任意的前k个字符中,1的个数不能少于0的个数,就需要把不符合条件的去掉。

我们可以假设在前 2 k + 1 2k+1 2k+1 个 字符中有 k k k 1 1 1 k + 1 k+1 k+1 0 0 0,此时,后 n + m − 2 k − 1 n+m-2k-1 n+m2k1 个字符中必然有 n − k n-k nk 1 1 1 m − k − 1 m-k-1 mk1 0 0 0将后面的 0 0 0 1 1 1取反,则此时变成了一个有 n + 1 n+1 n+1 1 1 1 m − 1 m-1 m1 0 0 0的字符串,因此有 C n + 1 n \mathrm{C}_{n+1}^n Cn+1n 种情况是不符合条件的。

之所以要取反,原因很简单,可以将字符串看作是一个 n + m n+m n+m 位的二进制数,应当可以看出,每一个前 2 k + 1 2k+1 2k+1 位中有 k k k 1 1 1 k + 1 k+1 k+1 0 0 0的二进制数都唯一对应一个有 n + 1 n+1 n+1 1 1 1 m − 1 m-1 m1 0 0 0的二进制数,可以得到不符合条件的总数量就是 C n + 1 n \mathrm{C}_{n+1}^n Cn+1n

所以答案就应该是 C n + m n − C n + 1 n \mathrm{C}_{n+m}^n-\mathrm{C}_{n+1}^n Cn+mnCn+1n

但是由于题目中 n n n m m m 范围较大,做除法时需要用逆元取模求组合数。

设组合数(不化简)中的分母为 d d d,分母为 m m m,因为 n , m < p n,m<p n,m<p,所以可以知道 gcd ⁡ ( d , p ) = 1 \gcd(d,p)=1 gcd(d,p)=1,且 p p p 为素数,可以由费马小定理得出: ( m / d ) % p = m % p ∗ p o w ( d , p − 2 ) % p (m/d)\%p=m\%p * pow(d,p-2)\%p (m/d)%p=m%ppow(d,p2)%p

最后再用快速幂处理一下即可。

#include<iostream>
#include<vector>
#include <cstdio>

using namespace std;
typedef long long ll;
const ll mod = 20100403;
ll n, m;

ll quickpow(ll a, ll b)
{
	ll ans = 1;
	while (b > 0)
	{
		if (b & 1)	ans = ans * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ans;
}

ll factorial(ll s)
{
	ll fs = 1;
	for (ll i = 2; i <= s; i++)
		fs = fs * i % mod;
	return fs;
}

ll c(ll x, ll y)
{
	ll fx = factorial(x), fy = factorial(y), fxy = factorial(x - y);
	fy = (fy * fxy) % mod;
	ll d = quickpow(fy, mod - 2);	
	fx = (fx * d) % mod;
	return fx;
}

int main()
{
	scanf("%lld %lld", &n, &m);
	printf("%lld\n", (c(n + m, n) - c(n + m, n + 1) + mod) % mod);
	return 0;
}

考虑到每次计算组合数都要调用 f a c t o r i a l factorial factorial函数求组合数,可以对组合数进行预处理,减少时间的开销。

Code
#define _CRT_SECURE_NO_WARNINGS				//VS上一般不支持直接使用scanf输入,需要加入该语句
#include<iostream>
#include<vector>
#include <cstdio>

using namespace std;
typedef long long ll;
const ll mod = 20100403;
const int N = 2e6 + 10;    //为了防止栈溢出,数组范围一般开数据范围的二倍
ll n, m;
ll fac[N];

void f()									//预处理组合数
{
	fac[1] = 1;
	fac[0] = 1;
	for (int i = 2; i <= N; i++)
		fac[i] = fac[i - 1] * i % mod;
}


ll quickpow(ll a, ll b)
{
	ll ans = 1;
	while (b > 0)
	{
		if (b & 1)	ans = ans * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ans;
}

ll c(ll x, ll y)
{
	ll fx = fac[x], fy = fac[y], fxy = fac[x - y];
	fy = (fy * fxy) % mod;
	ll d = quickpow(fy, mod - 2);			//利用费马小定理求组合数
	fx = (fx * d) % mod;
	return fx;
}

int main()
{
	f();

	scanf("%lld %lld", &n, &m);
	printf("%lld\n", (c(n + m, n) - c(n + m, n + 1) + mod) % mod);

	return 0;
}

将两种程序提交后比较一下

在这里插入图片描述
然而实际上时间并没有减少太多

通过分析其实也能看出来,其实factorial函数被调用的次数并不多,完全可以被忽略掉 (计算机:我超快的)

不过对于某些毒瘤题目来说,预处理组合数还是非常有用的

具体题目找不到惹,以后再说吧

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值