1. 程式人生 > >洛谷 P1641 [SCOI2010]生成字串

洛谷 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) n-m>=0

如果影象到了x軸下方則存在k=n+m,n-m<0,1的個數小於0的個數 存在n-m<0

直接求影象在x軸上方的所有情況個數有點困難,根據對稱性可以轉化一下 影象有在x軸下方的情況可以看作折線到達y=-1 將影象到達y=-1之前的影象關於直線y=-1對稱翻折可以得到 關於y=-1對稱後

關於y=-1對稱後 從(0,0)到達y=-1,下降的次數比上升的次數多一次,翻折後,可以看成從(0,-2)到y=-1,上升的次數比下降的次數多一次,這樣,所有存在n-m小於0的情況數等於從(0,-2)上升n+1次下降m-1次的折線影象的所有情況個數:C(n+m,m-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;
}