生成字串(卡特蘭數應用)
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\) 位中有 \(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函式被呼叫的次數並不多,完全可以被忽略掉 (計算機:我超快的)
不過對於某些毒瘤題目來說,預處理組合數還是非常有用的