BZOJ1856: [Scoi2010]字符串

Description
lxhgww最近接到了一個生成字符串的任務,任務需要他把n個1和m個0組成字符串,但是任務還要求在組成的字符串中,在任意的前k個字符中,1的個數不能少於0的個數。現在lxhgww想要知道滿足要求的字符串共有多少個,聰明的程序員們,你們能幫助他嗎?
Input
輸入數據是一行,包括2個數字n和m
Output
輸出數據是一行,包括1個數字,表示滿足要求的字符串數目,這個數可能會很大,只需輸出這個數除以20100403的餘數
Sample Input
2 2
Sample Output
2
HINT
【數據範圍】
對於30%的數據,保證1<=m<=n<=1000
對於100%的數據,保證1<=m<=n<=1000000
Source
Day2

20100403……看似隨意的數字竟然是個素數。此題爲最標準的卡特蘭數,以下內容摘自百度百科

問題1的描述:有n個1和m個-1(n>m),共n+m個數排成一列,滿足對所有0<=k<=n+m的前k個數的部分和Sk > 0的排列數。
問題等價爲在一個格點陣列中,從(0,0)點走到(n,m)點且不經過對角線x==y的方法數(x > y)。
考慮情況I:第一步走到(0,1),這樣從(0,1)走到(n,m)無論如何也要經過x==y的點,這樣的方法數爲(( n+m-1,m-1 ));
考慮情況II:第一步走到(1,0),又有兩種可能:
a. 不經過x==y的點;(所要求的情況)
b. 經過x==y的點,我們構造情況II.b和情況I的一一映射,說明II.b和I的方法數是一樣的。設第一次經過x==y的點是(x1,y1),將(0,0)到(x1,y1)的路徑沿對角線翻折,於是唯一對應情況I的一種路徑;對於情況I的一條路徑,假設其與對角線的第一個焦點是(x2,y2),將(0,0)和(x2,y2)之間的路徑沿對角線翻折,唯一對應情況II.b的一條路徑。
問題的解就是總的路徑數 ((n+m, m)) - 情況I的路徑數 - 情況II.b的路徑數。
((n+m , m)) - 2*((n+m-1, m-1)) 或:((n+m-1 , m)) - ((n+m-1 , m-1))
問題2的描述:有n個1和m個-1(n>=m),共n+m個數排成一列,滿足對所有0<=k<=n+m的前k個數的部分和Sk >= 0的排列數。(和問題1不同之處在於此處部分和可以爲0,這也是更常見的情況)
問題等價爲在一個格點陣列中,從(0,0)點走到(n,m)點且不穿過對角線x==y的方法數(可以走到x==y的點)。
把(n,m)點變換到(n+1,m)點,問題變成了問題1。 方法數爲: ((n+m+1, m)) - 2*((n+m+1-1, m-1))
或:((n+m+1-1, m)) - ((n+m+1-1, m-1))

一開始很傻逼地寫了個組合數取模,結果T了。又想了一遍數據範圍和時間複雜度,改成組合數公式即暴力階乘結果A了……
組合數公式

#include<cstdio>
#include<math.h>
#include<iostream>
using namespace std;
typedef long long LL;
const int p=20100403;
int n,m;
LL ans;
LL quick_pow(LL a,LL b)//快速冪
{
    LL t=1;
    for (;b;b>>=1,a=(a*a)%p)
    if (b&1) t=(t*a)%p;
    return t;
}
LL jc(int x)//階乘
{
    LL t=1;
    for (int i=2;i<=x;++i)
        t=(t*i)%p;
    return t;
}
LL ny(int x)//逆元(利用費馬小定理)
{
    return quick_pow(x,p-2);
}
LL C(int x,int y)//組合數
{
    return (jc(x)*ny(jc(y))%p*ny(jc(x-y))%p);
}
int main()
{
    scanf("%d%d",&n,&m);
    cout<<(C(n+m,n)-C(n+m,m-1)+p)%p;
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章