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

luogu P1641 生成字符串

Description

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

Solution

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

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

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

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

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

设组合数(不化简)中的分母为 \(d\),分母为 \(m\),因为 \(n,m<p\),所以可以知道 \(\gcd(d,p)=1\),且 \(p\) 为素数,可以由费马小定理得出:\((m/d)\%p=m\%p * pow(d,p-2)\%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;
}

考虑到每次计算组合数都要调用\(factorial\)函数求组合数,可以对组合数进行预处理,减少时间的开销。

Code

#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函数被调用的次数并不多,完全可以被忽略掉 (计算机:我超快的)

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

posted @ 2021-10-04 18:49  circletime  阅读(94)  评论(0)    收藏  举报