【洛谷P1641】[SCOI2010]生成字符串
題目描述
lxhgww最近接到了一個生成字符串的任務,任務需要他把n個1和m個0組成字符串,但是任務還要求在組成的字符串中,在任意的前k個字符中,1的個數不能少於0的個數。現在lxhgww想要知道滿足要求的字符串共有多少個,聰明的程序員們,你們能幫助他嗎?
輸入輸出格式
輸入格式:
輸入數據是一行,包括2個數字n和m
輸出格式:
輸出數據是一行,包括1個數字,表示滿足要求的字符串數目,這個數可能會很大,只需輸出這個數除以20100403的余數
輸入輸出樣例
輸入樣例#1:2 2輸出樣例#1:
2
說明
limitation
每點2秒
對於30%的數據,保證1<=m<=n<=1000
對於100%的數據,保證1<=m<=n<=1000000
來源:SCOI 2010
分析(轉載)
原文地址
寫的確實不錯。
首先,我們設選1為(1,1),選0為(1,-1)
目標就是(n+m,n-m)
總方案數為C(n+m,n),因為有n+m個位置,放n個1
然後要減去不合法的即線路通過y=-1的。將線路與y=-1交點的左邊沿著y=-1做對稱操作,則最後等價於從(0,-2)走到(n+m,n-m)的方案數
所以向上走n-m+2
則有x-y=n-m+2
x+y=n+m
x=n+1,y=m-1
所以不合法方案為C(n+m,n+1)
ans=C(n+m,n)-C(n+m,n+1)
求這些用模逆元,O(n)求解
另附上洛谷題解中的分析
原文地址
可以考慮把11的個數與00的個數的和看成xx坐標,11的個數與00的個數的差看成yy坐標,那麽如下圖:
向右上走(xx坐標加11,yy坐標加11)就表示這個字符選擇11。
向右下走(xx坐標加11,yy坐標減11)就表示這個字符選擇00。
這樣子,如果不考慮限制條件,就表示從(0,0)(0,0)走n+mn+m步到達(n+m,n-m)(n+m,n?m),這相當於從n+mn+m步中選出mm步向右下走,也就是C(n+m,m)C(n+m,m)。
考慮限制條件,任意前綴中11的個數不少於00的個數,也就是這條路徑不能經過直線y=-1y=?1。可以通過對稱性發現,從(0,0)(0,0)走到直線y=-1y=?1上的一點,相當於從(0,-2)(0,?2)走到該點。也就是說,路徑經過直線y=-1y=?1的方案數就是從(0,-2)(0,?2)走n+mn+m步到達(n+m,n-m)(n+m,n?m),這個方案數可以用組合數表示為C(n+m,m-1)C(n+m,m?1)。
所以最後結果為C(n+m,m)-C(n+m,m-1)C(n+m,m)?C(n+m,m?1)。對於組合數,可以預處理階乘後用乘法逆元計算。
代碼
我是用費馬小定理做的
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int p=20100403; typedef long long ll; int n,m; int fac[2000005]; int power(int a,int b) { int res=1,base=a; while(b) { if(b&1) res=(ll)res*(ll)base%(ll)p; base=(ll)base*(ll)base%(ll)p; b=b>>1; } return res; } int inv(int x) { return power(x,p-2);} void fct() { fac[0]=1; for(int i=1;i<=2000000;i++) fac[i]=(ll)fac[i-1]*(ll)i%(ll)p; } int main() { scanf("%d%d",&n,&m); fct(); int t1,t2,x1,x2,ans; t1=fac[n+m]; t2=(ll)fac[n]*(ll)fac[m]%(ll)p; x1=(ll)t1*(ll)inv(t2)%(ll)p; t2=(ll)fac[m-1]*(ll)fac[n+1]%(ll)p; x2=(ll)t1*(ll)inv(t2)%(ll)p; ans=(x1%p-x2%p+p)%p; printf("%d\n",ans); return 0; }
【洛谷P1641】[SCOI2010]生成字符串