1. 程式人生 > 其它 >生成字串(卡特蘭數應用)

生成字串(卡特蘭數應用)

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函式被呼叫的次數並不多,完全可以被忽略掉 (計算機:我超快的)

不過對於某些毒瘤題目來說,預處理組合數還是非常有用的