hdu 3944 DP? 组合数学与数论的结合

原题连接:http://acm.hdu.edu.cn/showproblem.php?pid=3944

题目大意:求杨辉三角中从塔尖到n行k列所经过的路径中,虽经过数字之和最小的一条路

经过观察,很容易想到的一个结论就是,这个最小值是从定点开始,然后到合适的位置45度斜着走到(n,k)的位置,也就是

//c(m,n) 表示n中选m个

c(0,n-k)+c(1,n-k+1)+c(2,n-k+2) + ...... + c(k,n) + n - k;

现在的关键就是求前面的那些组合数的和的公式

当时,我并不知道c(0,n-k)+c(1,n-k+1)+c(2,n-k+2) + ...... + c(k,n) 的公式

然后我就用组合数学的式子证明了,c(0,n-k)+c(1,n-k+1)+c(2,n-k+2) + ...... + c(k,n)  = c(k,n+1)

证明过程如下:

有G{c(k,n+k)} = 1 / (1-x)^(n+1);这个是组合数学里面关于生成函数的式子

现在我们有:G{c(m,n-k+m)} = 1 / (1-x)^(n-k+1)

即:c(m,n-l+m) 的生成函数是  A(x) = 1 / (1-x)^(n-k+1);

令:B(x) = A(0) + A(1) + A(2) +.....+A(x)

则:B(x) = A(x)/(1-x) = 1 / (1 - k)^(n-k+2); //这个是生成函数的一个重要性质

再次使用G{c(k,n+k)} = 1 / (1-x)^(n+1)

就可以求得:B(x) = G{c(k,n - k+1)} 所以B(x)的第k项的系数为c(k,n+1)


我原以为这个到这儿就要结束了,总结上面的就是,结果为 (c(k,n+1)+n-k) modp

哎,我刚刚学习数学啊,还是太幼稚了

由于n和k的值都很大,也就是说,求c(k,n+1)的时候还是一个很大的麻烦,怎么求呢??

这儿就有了一个 Lucas定理

定理是这样的:

A、B是非负整数,p是质数。AB写成p进制:A=a[n]a[n-1]...a[0],B=b[n]b[n-1]...b[0]。
则组合数C(A,B)与C(a[n],b[n])*C(a[n-1],b[n-1])*...*C(a[0],b[0])  mod p同余

Lucas定理的证明用到了数论的知识,我的数论还在起步的状态,我没有看懂!

然后就是求组合数连乘了

但是当我敲进了一个模板的时候,tle,我傻眼了,原来这个题需要预处理一下,因为,这儿的数据规模太大了

下面的东西,我看的那个解题报告我没看懂,包含的内容如下:筛素数,素数阶乘模逆    好像是这样的,我由于数论还没有看多少,没看懂


我是第一次推导这样的公式,如有不妥的地方,还望多多交流

贴一个代码吧:

#include <cstdio>
#include <cstring>
#include <vector>
#define maxn 10000
using namespace std;
int flag[maxn],pim[maxn],tol;
int p[1229][9973];
int ni[1229][9973];
int mp[10000];
void init()
{
    tol=0;
    for(int i=2; i<maxn; i++) //素数筛
    {
        if(!flag[i])  pim[tol++]=i;
        for(int j=0; j<tol&&i*pim[j]<maxn; j++)
        {
            flag[i*pim[j]]=1;
            if(i%pim[j]==0) break;
        }
    }
    for(int i=0; i<tol; i++) //对每一个素数求各阶乘模和逆
    {
        int k=pim[i];
        mp[k]=i;
        p[i][0]=1, p[i][1]=1;
        ni[i][0]=1,ni[i][1]=1;
        for(int j=2; j<k; j++) //从2!到(p-1)!
        {
            p[i][j]=j*p[i][j-1]%pim[i];
            ni[i][j]=-k/j*ni[i][k%j]%k;
            if(ni[i][j]<0) ni[i][j]+=k;
        }
    }
    for(int i=0; i<tol; i++) //求阶乘逆
    {
        int k=pim[i];
        for(int j=1; j<k; j++)
        {
            ni[i][j]=ni[i][j]*ni[i][j-1]%k;
        }
    }
}
int cal(int n,int m,int v)
{
    if(n<m) return 0;
    int x=mp[v];
    return p[x][n] * ni[x][m]%v * ni[x][n-m]%v;
}
int main()
{
    int n,k,p,ca=0;
    init();
    while(scanf("%d %d %d",&n,&k,&p)==3)
    {
        if(k>n/2) k=n-k;
        int res=1,u_u=n-k;
        n++;
        while(n&&k)
        {
            res=res*cal(n%p,k%p,p)%p;
            if(res==0) break;
            n/=p;
            k/=p;
        }
        printf("Case #%d: %d\n",++ca,(res+u_u)%p);
    }
    return 0;
}



你可能感兴趣的:(c)