题目:http://www.51nod.com/Challenge/Problem.html#!#problemId=1362
方法一:
设 a 是向下走的步数、 b 是向右下走的步数、 c 是向下走的步数。如果是走到第 j 列的方案数的话,有:
( a+b = n ) ( b+c = j )
所以枚举 a 和 j , b 和 c 的值就是确定的,可以用组合数算:
( sumlimits_{i=0}^{n}sumlimits_{j=0}^{m}C_{i+j}^{i}*C_{j}^{n-i} )
( = sumlimits_{i=0}^{n}sumlimits_{j=0}^{m} frac{(i+j)!}{i!j!} * frac{j!}{(n-i)!(i+j-n)!} )
( = sumlimits_{i=0}^{n}sumlimits_{j=0}^{m} frac{(i+j)!}{i!(n-i)!(i+j-n)!} )
一看就是要把只和 i 有关的提到前面。而且分子和分母好像能凑成新的组合数,所以弄一个 ( n! )
( = sumlimits_{i=0}^{n} frac{n!}{i!(n-i)!} sumlimits_{j=0}^{m} frac{(i+j)!}{n!(i+j-n)!} )
( = sumlimits_{i=0}^{n} C_{n}^{i} sumlimits_{j=0}^{m} C_{i+j}^{n} )
( = sumlimits_{i=0}^{n} C_{n}^{i} * C_{i+m+1}^{n+1} )
但 i+m+1 很大,而且模数也不是质数。所以用扩展Lucas。
但它的 pk 可以很大,会TLE。总之弄了半天还是会T一个点。反正本来复杂度也不对……
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; const int N=805,M=1e5+5; int n,m,mod,tot,p[M],pk[M],ans; void init() { int d=mod; tot=0; for(int i=2;i*i<=d;i++) if(d%i==0) { p[++tot]=i;pk[tot]=1; while(d%i==0)d/=i,pk[tot]*=i; } if(d>1)p[++tot]=pk[tot]=d; } int pw(int x,int k,int md) {int ret=1;while(k){if(k&1)ret=(ll)ret*x%mod;x=(ll)x*x%mod;k>>=1;}return ret;} void exgcd(int a,int b,int &x,int &y) {if(!b){x=1;y=0;return;} exgcd(b,a%b,y,x); y-=a/b*x;} int inv(int a,int md){int x,y;exgcd(a,md,x,y);return (x+md)%md;} int multi(int n,int p,int pk) { if(!n)return 1; int sm=1; for(int i=2;i<pk;i++)if(i%p)sm=(ll)sm*i%pk;//if sm=pw(sm,n/pk,pk); for(int i=2,j=n%pk;i<=j;i++)if(i%p)sm=(ll)sm*i%pk;// return (ll)sm*multi(n/p,p,pk)%pk; } int exlcs(int n,int m,int p,int pk) { int sm=0; for(int i=n;i;i/=p)sm+=i/p; for(int i=m;i;i/=p)sm-=i/p; for(int i=n-m;i;i/=p)sm-=i/p; return (ll)pw(p,sm,pk)*multi(n,p,pk)%pk*inv(multi(m,p,pk),pk)%pk*inv(multi(n-m,p,pk),pk)%pk; } int C(int n,int m,int p) { if(n<m)return 0; int ret,sm=1; for(int i=2;i<=n;i++)sm=(ll)sm*i%p; ret=sm; sm=1; for(int i=2;i<=m;i++)sm=(ll)sm*i%p; ret=(ll)ret*pw(sm,p-2,p)%p; sm=1; for(int i=2;i<=n-m;i++)sm=(ll)sm*i%p; ret=(ll)ret*pw(sm,p-2,p)%p; return ret; } int lcs(int n,int m,int p) { if(n<m)return 0; if(!m||n==m)return 1; return (ll)C(n%p,m%p,p)*lcs(n/p,m/p,p)%p; } int calc(int n,int m) { if(n<m)return 0;///// int ret=0; for(int i=1;i<=tot;i++) { if(p[i]==pk[i]) { ret=(ret+(ll)lcs(n,m,p[i])*(mod/pk[i])%mod*inv(mod/pk[i],pk[i]))%mod; } else ret=(ret+(ll)exlcs(n,m,p[i],pk[i])*(mod/pk[i])%mod*inv(mod/pk[i],pk[i]))%mod; } return ret; } int main() { while(scanf("%d%d%d",&n,&m,&mod)==3) { init(); ans=0; for(int i=0;i<=n;i++) { ans=(ans+(ll)calc(n,i)*calc(i+m+1,n+1))%mod; } printf("%d ",ans); } return 0; }
发现别人都不是这样写的。
因为 ( C_{i+m+1}^{n+1} ) 虽然 i+m+1 很大,但n+1 很小,所以可以枚举分子上的数(约掉分母里很大的那一项之后只剩下 n 项了!),一边把含的 p 拿出来之类的。
注意不要用求阶乘里 p 的个数的方法求好总的 p 的个数之后在枚举的时候跳过 %p==0 的数。因为可能那个数除掉一些 p 之后有剩下的。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; const int N=805,M=1e5+5; int n,m,mod,tot,p[M],pk[M],ans; void init() { int d=mod; tot=0; for(int i=2;i*i<=d;i++) if(d%i==0) { p[++tot]=i;pk[tot]=1; while(d%i==0)d/=i,pk[tot]*=i; } if(d>1)p[++tot]=pk[tot]=d; } int pw(int x,int k,int md) {int ret=1;while(k){if(k&1)ret=(ll)ret*x%mod;x=(ll)x*x%mod;k>>=1;}return ret;} void exgcd(int a,int b,int &x,int &y) {if(!b){x=1;y=0;return;} exgcd(b,a%b,y,x); y-=a/b*x;} int inv(int a,int md){int x,y;exgcd(a,md,x,y);return (x%md+md)%md;} int C(int n,int m,int p,int pk) { int nm=0,ret=1; for(int i=n,j=1;j<=m;i--,j++) { int a=i,b=j; while(a%p==0)nm++,a/=p; while(b%p==0)nm--,b/=p; ret=(ll)ret*a%pk*inv(b,pk)%pk; } ret=(ll)ret*pw(p,nm,pk)%pk; return ret; } int calc(int n,int m) { if(n<m)return 0;///// int ret=0; for(int i=1;i<=tot;i++) { ret=(ret+(ll)C(n,m,p[i],pk[i])*(mod/pk[i])%mod*inv(mod/pk[i],pk[i]))%mod; } return ret; } int main() { while(scanf("%d%d%d",&n,&m,&mod)==3) { init(); ans=0; for(int i=0;i<=n;i++) { ans=(ans+(ll)calc(n,i)*calc(i+m+1,n+1))%mod; } printf("%d ",ans); } return 0; }
方法二:
设 dp[ i ][ j ] 表示走到第 i 行第 j 列的方案数,则有 dp[ i ][ j ] = dp[ i-1 ][ j ] + dp[ i ][ j-1 ] + dp[ i-1 ][ j-1 ] ;
用差分的方法判断一下,发现 dp[ i ] 是一个 i 次的多项式。(设原数列为第0次差分后数列,若差分 k 次后数列变成常数列(即每一项值都一样),则为 k 次多项式)
设 ( s[ i ][ j ] = sumlimits_{k=0}^{j} dp[ i ][ k ] ) ,则 s[ i ] 是一个 i+1 次多项式。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; const int N=805,M=35; int n,m,mod,dp[N][N]; void upd(int &x){x>=mod?x-=mod:0;} int main() { while(scanf("%d%d%d",&n,&m,&mod)==3) { memset(dp,0,sizeof dp); int d=min(n+1,m); dp[0][0]=1; for(int i=0;i<=n;i++) for(int j=0;j<=d;j++) { if(i)dp[i][j]+=dp[i-1][j],upd(dp[i][j]); if(j)dp[i][j]+=dp[i][j-1],upd(dp[i][j]); if(i&&j)dp[i][j]+=dp[i-1][j-1],upd(dp[i][j]); } for(int j=0;j<=d;j++)dp[n][j]+=dp[n][j-1],upd(dp[n][j]); int lm=1000; for(int i=1,R=d-1;i<=lm;i++,R--) { int flag=1; for(int j=0;j<=R;j++) { dp[n][j]=dp[n][j+1]-dp[n][j]; if(j&&dp[n][j]!=dp[n][j-1])flag=0; } if(flag){printf("%d ",i);break;} } } return 0; }
所以可以用拉格朗日插值。
注意 ( i - j ) 可能没有逆元,同样采用把 mod 质因数(只有log(mod)个质因数!)分解、最后乘上 pk 的方法。
注意要除的东西不能先累乘起来最后再除掉。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; const int N=805,M=35; int n,m,mod,tot,dp[N][N],p[M],nm[M],phi; void upd(int &x){x>=mod?x-=mod:0;} int pw(int x,int k) {int ret=1;while(k){if(k&1)ret=(ll)ret*x%mod;x=(ll)x*x%mod;k>>=1;}return ret;} void init() { tot=0; phi=mod; int k=mod; for(int i=2;i*i<=k;i++) if(k%i==0) { p[++tot]=i; phi/=i; phi*=(i-1); while(k%i==0)k/=i; } if(k>1)p[++tot]=k,phi/=k,phi*=(k-1); } void add(int a,int fx,int &ret) { for(int i=1;i<=tot;i++) { while(a%p[i]==0)nm[i]+=fx,a/=p[i]; } fx==1? ret=(ll)ret*a%mod : ret=(ll)ret*pw(a,phi-1)%mod ; } int cz(int ret) { for(int i=1;i<=tot;i++) { ret=(ll)ret*pw(p[i],nm[i])%mod; nm[i]=0; } return ret; } int main() { while(scanf("%d%d%d",&n,&m,&mod)==3) { memset(dp,0,sizeof dp); int d=min(n+1,m); dp[0][0]=1; for(int i=0;i<=n;i++) for(int j=0;j<=d;j++) { if(i)dp[i][j]+=dp[i-1][j],upd(dp[i][j]); if(j)dp[i][j]+=dp[i][j-1],upd(dp[i][j]); if(i&&j)dp[i][j]+=dp[i-1][j-1],upd(dp[i][j]); } for(int i=1;i<=d;i++) dp[n][i]+=dp[n][i-1],upd(dp[n][i]); if(m==d){printf("%d ",dp[n][m]);continue;} init(); int ans=0; for(int i=0;i<=d;i++) { int ret=1; for(int j=0;j<=d;j++) { if(i==j)continue; add(m-j,1,ret); add(i-j,-1,ret); } ans=(ans+(ll)dp[n][i]*cz(ret))%mod; } if(ans<0)ans+=mod; printf("%d ",ans); } return 0; }