hdu 3944 DP? (Lucas定理+预处理)

题意:给你一个杨辉三角,每一行和每一列都是从0开始,给两个数n,k,代表杨辉三角的第n行第k列,和一个素数p,让你计算从(0,0)走到(n,k)的最小花费sum%p;

思路:让花费尽量小,那就让走过的1尽量多,我们可以有两种走法

第一种:先向下走再斜着走

第二种:想斜着走再向下走

第一种分析:我们走k+1个1,然后走C(k+1,k)+C(k+2,k)+...+C(n,k),我在这个式子中加入C(k+1,k+1),根据C(n,m)=C(n-1,m)+C(n-1,m-1),我们可以将这个式子合并,最后等于C(n+1,k+1),由于我们之前加入了C(k+1,k+1),这样第一种方法的最终结果是k+C(n+1,k+1)

第二种分析:我们先走了n-k个1,然后走C(k,0)+C(k+1,1)+C(k+2,2)+...+C(n,k),用C(k+1,0)把C(k,0)替换掉,将式子合并,最后等于C(n+1,k),最终结果是n-k+C(n+1,k)

这两种方法哪种走过的1少就用哪种,只需比较n与n-k的大小就行

当n<n-k就用第一种,反之用第二种

由于n,k的范围比较大,而p又是素数,所以用Lucas定理求组合数

代码:

#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstring>
#define ll long long

using namespace std;
const int maxn=10005;
bool isprime[maxn];
int prime[maxn],prime_th[maxn];
int cnt;
int  inv_by_prime_th[maxn][maxn];
int  f_use_to_inv[maxn][maxn];

void get_prime_and_primeth()
{
    cnt=0;
    memset(isprime,true,sizeof(isprime));
    for(int i=2;i<maxn;i++)
    {
        if(isprime[i])
        {
            prime[++cnt]=i;
            prime_th[i]=cnt;
            for(int j=i+i;j<maxn;j+=i)
            {
                isprime[j]=false;
            }
        }
    }
}

int qmod(int a,int b,int mod)
{
    int ans=1;
    a=a%mod;
    while(b)
    {
        if(b&1)
        {
            ans=(ans*a)%mod;
            b--;
        }
        b=b/2;
        a=(a*a)%mod;
    }
    return ans;
}

void init()
{
    for(int i=1;i<=cnt;i++)
    {
        inv_by_prime_th[i][0]=f_use_to_inv[i][0]=1;
        for(int j=1;j<prime[i];j++)
        {
            f_use_to_inv[i][j]=(f_use_to_inv[i][j-1]*j)%prime[i];
            inv_by_prime_th[i][j]=qmod(f_use_to_inv[i][j],prime[i]-2,prime[i]);
        }
    }
}

int com(int n,int m,int p)
{
    if(n<m) return 0;
    else if(n==m) return 1;
    else
    {
        int primeth=prime_th[p];
        return (f_use_to_inv[primeth][n]*((inv_by_prime_th[primeth][m]*inv_by_prime_th[primeth][n-m])%p))%p;
    }
}

int Lucas(int n,int m,int p)
{
    if(m==0) return 1;
    else return (com(n%p,m%p,p)*Lucas(n/p,m/p,p))%p;
}

int main(int argc, char const *argv[])
{
    int cas=1;
    get_prime_and_primeth();
    init();
    //ios::sync_with_stdio(false);
    int n,m,p;
    while(scanf("%lld %lld %lld",&n,&m,&p)!=-1)
    {
        printf("Case #%d: ",cas++);
        if(m<=n/2)  m=n-m;
        printf("%lld\n",(Lucas(n+1,m+1,p)+m%p)%p);
    }
    return 0;
}

 

posted @ 2017-06-11 20:35  simpleknight  阅读(427)  评论(0编辑  收藏  举报