洛谷 P1641 [SCOI2010]生成字串
洛谷 P1641 [SCOI2010]生成字串
題目描述 lxhgww最近接到了一個生成字串的任務,任務需要他把n個1和m個0組成字串,但是任務還要求在組成的字串中,在任意的前k個字元中,1的個數不能少於0的個數。現在lxhgww想要知道滿足要求的字串共有多少個,聰明的程式設計師們,你們能幫助他嗎?
輸入格式: 輸入資料是一行,包括2個數字n和m
輸出格式: 輸出資料是一行,包括1個數字,表示滿足要求的字串數目,這個數可能會很大,只需輸出這個數除以20100403的餘數。
輸入輸出樣例
輸入 | 輸出 |
---|---|
2 2 | 2 |
9 7 | 3432 |
說明 limitation 每點2秒 對於30%的資料,保證1<=m<=n<=1000 對於100%的資料,保證1<=m<=n<=1000000 來源:SCOI 2010
題目標籤:組合數學 逆元
聽說這好像時一道高考題orz
用n代表1的個數,m代表0的個數,設 x=n+m,y=n-m,建立直角座標系,生成字串就可以用從(0,0)到(n+m,n-m)的上升n次下降m次的折線表示,所有的情況個數為C(n+m,m)。
要滿足任意的前k個字元中,1的個數大於等於0的個數,也就是影象要始終在x軸的上方(n-m>=0)
如果影象到了x軸下方則存在k=n+m,n-m<0,1的個數小於0的個數
直接求影象在x軸上方的所有情況個數有點困難,根據對稱性可以轉化一下 影象有在x軸下方的情況可以看作折線到達y=-1 將影象到達y=-1之前的影象關於直線y=-1對稱翻折可以得到
所以答案就為C(n+m,m)-C(n+m,m-1)
然後就是處理組合數的問題了,這個資料範圍,用楊輝三角打表不現實,只能逆元打表了,之後用公式求組合數
逆元打表
在模質數M下,求1~n的逆元(n<M,M為奇質數): inv[i] = (M-M/i) * inv[M%i]%M
板子:
const int maxn = 1e6+5; int inv[maxn]; void inverse(int n,int M){ inv[1]=1; for(int i=2;i<=n;i++){ inv[i]=(long long)(M-M/i)*inv[M%i]%M; } }
推導過程: 由 (M/i) * i + M%i = 0 (mod M)
=> -(M/i) * i = M%i (mod M) 兩邊同時除以 M%i /* i
=> -(M/i) * (1/(M%i)) = 1/(i) (mod M)
即:-(M/i) * inv[M%i] = inv[i] (mod M)
所以:inv[i]=(M-M/i) * inv[M%i]%M
程式碼:
#include <iostream>
#define LL long long
using namespace std;
const LL M = 20100403;
const int N = 1e6+10;
int inv[N],fac[N],n,m;
void init(int n){//逆元打表
fac[0]=1;
for(int i=1;i<=n;i++){//階乘
fac[i]=(LL)fac[i-1]*i%M;
}
inv[1]=1;
for(int i=2;i<=n;i++){//1~n在 M 下的逆元
inv[i]=(LL)(M-M/i)*inv[M%i]%M;
}
for(int i=2;i<=n;i++){//1~n的階乘在 M 下的逆元
inv[i]=(LL)inv[i]*inv[i-1]%M;
}
}
int comb(int a,int b){//求組合數
if(a==b||b==0) return 1;
if(b<0||b>a) return 0;
return (LL)fac[a]*inv[b]%M*inv[a-b]%M;//組合數公式
}
int main(){//主函式
cin>>n>>m;
init(n+m);
if(n<m||m+n==0){
cout<<0;
return 0;
}
int an=(comb(m+n,m)-comb(m+n,m-1))%M;
cout<<an;
return 0;
}