题目:http://www.51nod.com/Challenge/Problem.html#!#problemId=1362

方法一:

  设 a 是向下走的步数、 b 是向右下走的步数、 c 是向下走的步数。如果是走到第 j 列的方案数的话,有:

  \( a+b = n \)    \( b+c = j \)

  所以枚举 a 和 j , b 和 c 的值就是确定的,可以用组合数算:

  \( \sum\limits_{i=0}^{n}\sum\limits_{j=0}^{m}C_{i+j}^{i}*C_{j}^{n-i} \)

  \( = \sum\limits_{i=0}^{n}\sum\limits_{j=0}^{m} \frac{(i+j)!}{i!j!} * \frac{j!}{(n-i)!(i+j-n)!} \)

  \( = \sum\limits_{i=0}^{n}\sum\limits_{j=0}^{m} \frac{(i+j)!}{i!(n-i)!(i+j-n)!} \)

  一看就是要把只和 i 有关的提到前面。而且分子和分母好像能凑成新的组合数,所以弄一个 \( n! \)

  \( = \sum\limits_{i=0}^{n} \frac{n!}{i!(n-i)!} \sum\limits_{j=0}^{m} \frac{(i+j)!}{n!(i+j-n)!} \)

  \( = \sum\limits_{i=0}^{n} C_{n}^{i} \sum\limits_{j=0}^{m} C_{i+j}^{n} \)

  \( = \sum\limits_{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=,M=1e5+;
int n,m,mod,tot,p[M],pk[M],ans;
void init()
{
int d=mod; tot=;
for(int i=;i*i<=d;i++)
if(d%i==)
{
p[++tot]=i;pk[tot]=;
while(d%i==)d/=i,pk[tot]*=i;
}
if(d>)p[++tot]=pk[tot]=d;
}
int pw(int x,int k,int md)
{int ret=;while(k){if(k&)ret=(ll)ret*x%mod;x=(ll)x*x%mod;k>>=;}return ret;}
void exgcd(int a,int b,int &x,int &y)
{if(!b){x=;y=;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 ;
int sm=;
for(int i=;i<pk;i++)if(i%p)sm=(ll)sm*i%pk;//if
sm=pw(sm,n/pk,pk);
for(int i=,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=;
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 ;
int ret,sm=;
for(int i=;i<=n;i++)sm=(ll)sm*i%p; ret=sm;
sm=; for(int i=;i<=m;i++)sm=(ll)sm*i%p;
ret=(ll)ret*pw(sm,p-,p)%p;
sm=; for(int i=;i<=n-m;i++)sm=(ll)sm*i%p;
ret=(ll)ret*pw(sm,p-,p)%p;
return ret;
}
int lcs(int n,int m,int p)
{
if(n<m)return ; if(!m||n==m)return ;
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 ;/////
int ret=;
for(int i=;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)==)
{
init(); ans=;
for(int i=;i<=n;i++)
{
ans=(ans+(ll)calc(n,i)*calc(i+m+,n+))%mod;
}
printf("%d\n",ans);
}
return ;
}

  发现别人都不是这样写的。

  因为 \( 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=,M=1e5+;
int n,m,mod,tot,p[M],pk[M],ans;
void init()
{
int d=mod; tot=;
for(int i=;i*i<=d;i++)
if(d%i==)
{
p[++tot]=i;pk[tot]=;
while(d%i==)d/=i,pk[tot]*=i;
}
if(d>)p[++tot]=pk[tot]=d;
}
int pw(int x,int k,int md)
{int ret=;while(k){if(k&)ret=(ll)ret*x%mod;x=(ll)x*x%mod;k>>=;}return ret;}
void exgcd(int a,int b,int &x,int &y)
{if(!b){x=;y=;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=,ret=;
for(int i=n,j=;j<=m;i--,j++)
{
int a=i,b=j;
while(a%p==)nm++,a/=p;
while(b%p==)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 ;/////
int ret=;
for(int i=;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)==)
{
init(); ans=;
for(int i=;i<=n;i++)
{
ans=(ans+(ll)calc(n,i)*calc(i+m+,n+))%mod;
}
printf("%d\n",ans);
}
return ;
}

方法二:

  设 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 ] = \sum\limits_{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=,M=;
int n,m,mod,dp[N][N];
void upd(int &x){x>=mod?x-=mod:;}
int main()
{
while(scanf("%d%d%d",&n,&m,&mod)==)
{
memset(dp,,sizeof dp);
int d=min(n+,m); dp[][]=;
for(int i=;i<=n;i++)
for(int j=;j<=d;j++)
{
if(i)dp[i][j]+=dp[i-][j],upd(dp[i][j]);
if(j)dp[i][j]+=dp[i][j-],upd(dp[i][j]);
if(i&&j)dp[i][j]+=dp[i-][j-],upd(dp[i][j]);
} for(int j=;j<=d;j++)dp[n][j]+=dp[n][j-],upd(dp[n][j]);
int lm=;
for(int i=,R=d-;i<=lm;i++,R--)
{
int flag=;
for(int j=;j<=R;j++)
{
dp[n][j]=dp[n][j+]-dp[n][j];
if(j&&dp[n][j]!=dp[n][j-])flag=;
}
if(flag){printf("%d\n",i);break;}
}
}
return ;
}

判断

  所以可以用拉格朗日插值。

  注意 ( i - j ) 可能没有逆元,同样采用把 mod 质因数(只有log(mod)个质因数!)分解、最后乘上 pk 的方法。

  注意要除的东西不能先累乘起来最后再除掉。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=,M=;
int n,m,mod,tot,dp[N][N],p[M],nm[M],phi;
void upd(int &x){x>=mod?x-=mod:;}
int pw(int x,int k)
{int ret=;while(k){if(k&)ret=(ll)ret*x%mod;x=(ll)x*x%mod;k>>=;}return ret;}
void init()
{
tot=; phi=mod; int k=mod;
for(int i=;i*i<=k;i++)
if(k%i==)
{
p[++tot]=i;
phi/=i; phi*=(i-);
while(k%i==)k/=i;
}
if(k>)p[++tot]=k,phi/=k,phi*=(k-);
}
void add(int a,int fx,int &ret)
{
for(int i=;i<=tot;i++)
{
while(a%p[i]==)nm[i]+=fx,a/=p[i];
}
fx==? ret=(ll)ret*a%mod : ret=(ll)ret*pw(a,phi-)%mod ;
}
int cz(int ret)
{
for(int i=;i<=tot;i++)
{
ret=(ll)ret*pw(p[i],nm[i])%mod;
nm[i]=;
}
return ret;
}
int main()
{
while(scanf("%d%d%d",&n,&m,&mod)==)
{
memset(dp,,sizeof dp);
int d=min(n+,m); dp[][]=;
for(int i=;i<=n;i++)
for(int j=;j<=d;j++)
{
if(i)dp[i][j]+=dp[i-][j],upd(dp[i][j]);
if(j)dp[i][j]+=dp[i][j-],upd(dp[i][j]);
if(i&&j)dp[i][j]+=dp[i-][j-],upd(dp[i][j]);
}
for(int i=;i<=d;i++)
dp[n][i]+=dp[n][i-],upd(dp[n][i]);
if(m==d){printf("%d\n",dp[n][m]);continue;} init(); int ans=;
for(int i=;i<=d;i++)
{
int ret=;
for(int j=;j<=d;j++)
{
if(i==j)continue;
add(m-j,,ret); add(i-j,-,ret);
}
ans=(ans+(ll)dp[n][i]*cz(ret))%mod;
}
if(ans<)ans+=mod;
printf("%d\n",ans);
}
return ;
}

51nod 1362 搬箱子——[ 推式子+组合数计算方法 ] [ 拉格朗日插值 ]的更多相关文章

  1. 51Nod 1362 搬箱子 —— 组合数(非质数取模) (差分TLE)

    题目:http://www.51nod.com/Challenge/Problem.html#!#problemId=1362 首先,\( f[i][j] \) 是一个 \( i \) 次多项式: 如 ...

  2. luogu P4948 数列求和 推式子 简单数学推导 二项式 拉格朗日插值

    LINK:数列求和 每次遇到这种题目都不太会写.但是做法很简单. 终有一天我会成功的. 考虑类似等比数列求和的东西 帽子戏法一下. 设\(f(k)=\sum_{i=1}^ni^ka^i\) 考虑\(a ...

  3. [AHOI2009]中国象棋 DP,递推,组合数

    DP,递推,组合数 其实相当于就是一个递推推式子,然后要用到一点组合数的知识 一道很妙的题,因为不能互相攻击,所以任意行列不能有超过两个炮 首先令f[i][j][k]代表前i行,有j列为一个炮,有k列 ...

  4. 洛谷 P6031 - CF1278F Cards 加强版(推式子+递推)

    洛谷题面传送门 u1s1 这个推式子其实挺套路的吧,可惜有一步没推出来看了题解 \[\begin{aligned} res&=\sum\limits_{i=0}^ni^k\dbinom{n}{ ...

  5. 51Nod1362 搬箱子 排列组合,中国剩余定理

    原文链接https://www.cnblogs.com/zhouzhendong/p/51Nod1362.html 题目传送门 - 51Nod1362 题意 题解 首先考虑枚举斜着走了几次.假设走了 ...

  6. bzoj 3157 && bzoj 3516 国王奇遇记——推式子

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3157 https://www.lydsy.com/JudgeOnline/problem.p ...

  7. [HAOI2007]分割矩阵 DP+推式子

    发现最近好少写博客啊(其实是各种摆去了) 更一点吧 这道题要求最小化均方差,其实凭直觉来说就是要使每个块分的比较均匀一点,但是单单想到想到这些还是不够的, 首先f[i][j][k][l][t]表示以( ...

  8. P3768 简单的数学题 杜教筛+推式子

    \(\color{#0066ff}{ 题目描述 }\) 由于出题人懒得写背景了,题目还是简单一点好. 输入一个整数n和一个整数p,你需要求出(\(\sum_{i=1}^n\sum_{j=1}^n ij ...

  9. bzoj 3157 & bzoj 3516 国王奇遇记 —— 推式子

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3157 https://www.lydsy.com/JudgeOnline/problem.p ...

随机推荐

  1. LeetCode——Sum of Two Integers

    LeetCode--Sum of Two Integers Question Calculate the sum of two integers a and b, but you are not al ...

  2. java+opencv+intellij idea实现人脸识别

    首先当然是需要安装opencv了,我用的是opencv2.4.13.下载完之后就可以直接安装了,安装过程也很简单,直接下一步下一步就好,我就不上图了. 接下来在opencv下找到jar包,比如我直接安 ...

  3. vue2项目中better-scroll 插件使用时候页面不滚动

    参考这里 1.外面包裹层的高度没有设置或者条目的高度没有超过外面包裹层的高度 2.初始化BetterScroll的时机不对,当前元素还没有正常渲染出来,导致BetterScroll的高度的计算错误

  4. 006——VUE中的内容与属性中使用javascript表达式的方法

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. Qt DLL总结

    (转自:http://qimo601.iteye.com/blog/1397936) QT动态链接库的调用方法,主要包括: 1.显式链接DLL,调用DLL的全局函数,采用Qt的QLibrary方法 2 ...

  6. listView使用小技巧P66--P76

    1.设置分割线高度和颜色 android:divider="@android:color/darker_gray" android:dividerHeight="10dp ...

  7. SpringCloud教程 | 第九篇: 服务链路追踪(Spring Cloud Sleuth)

    版权声明:本文为博主原创文章,欢迎转载,转载请注明作者.原文超链接 ,博主地址:http://blog.csdn.net/forezp. http://blog.csdn.net/forezp/art ...

  8. h5启动原生APP总结

    许久没有写博客了,最近有个H5启动APP原生页面的需求,中间遇上一些坑,看了些网上的实现方案,特意来总结下 一.需要判断客户端的平台以及是否在微信浏览器中访问 1.客户端判断 在启动APP时,Andr ...

  9. Android 静默安装/后台安装& Root permission

    Android 静默安装/后台安装& Root permission 静默安装其实很简单,今天在网上找资料找半天都说的很复杂,什么需要系统安装权限.调用系统隐藏的api.需要系统环境下编译.需 ...

  10. SQL-left(right,inner) join

    left join(左联接) :返回包括左表中的所有记录和右表中联结字段相等的记录 right join(右联接) :返回包括右表中的所有记录和左表中联结字段相等的记录inner join(等值连接) ...