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; }