51nod 1362 搬箱子——[ 推式子+组合数计算方法 ] [ 拉格朗日插值 ]
题目: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 搬箱子——[ 推式子+组合数计算方法 ] [ 拉格朗日插值 ]的更多相关文章
- 51Nod 1362 搬箱子 —— 组合数(非质数取模) (差分TLE)
题目:http://www.51nod.com/Challenge/Problem.html#!#problemId=1362 首先,\( f[i][j] \) 是一个 \( i \) 次多项式: 如 ...
- luogu P4948 数列求和 推式子 简单数学推导 二项式 拉格朗日插值
LINK:数列求和 每次遇到这种题目都不太会写.但是做法很简单. 终有一天我会成功的. 考虑类似等比数列求和的东西 帽子戏法一下. 设\(f(k)=\sum_{i=1}^ni^ka^i\) 考虑\(a ...
- [AHOI2009]中国象棋 DP,递推,组合数
DP,递推,组合数 其实相当于就是一个递推推式子,然后要用到一点组合数的知识 一道很妙的题,因为不能互相攻击,所以任意行列不能有超过两个炮 首先令f[i][j][k]代表前i行,有j列为一个炮,有k列 ...
- 洛谷 P6031 - CF1278F Cards 加强版(推式子+递推)
洛谷题面传送门 u1s1 这个推式子其实挺套路的吧,可惜有一步没推出来看了题解 \[\begin{aligned} res&=\sum\limits_{i=0}^ni^k\dbinom{n}{ ...
- 51Nod1362 搬箱子 排列组合,中国剩余定理
原文链接https://www.cnblogs.com/zhouzhendong/p/51Nod1362.html 题目传送门 - 51Nod1362 题意 题解 首先考虑枚举斜着走了几次.假设走了 ...
- bzoj 3157 && bzoj 3516 国王奇遇记——推式子
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3157 https://www.lydsy.com/JudgeOnline/problem.p ...
- [HAOI2007]分割矩阵 DP+推式子
发现最近好少写博客啊(其实是各种摆去了) 更一点吧 这道题要求最小化均方差,其实凭直觉来说就是要使每个块分的比较均匀一点,但是单单想到想到这些还是不够的, 首先f[i][j][k][l][t]表示以( ...
- P3768 简单的数学题 杜教筛+推式子
\(\color{#0066ff}{ 题目描述 }\) 由于出题人懒得写背景了,题目还是简单一点好. 输入一个整数n和一个整数p,你需要求出(\(\sum_{i=1}^n\sum_{j=1}^n ij ...
- bzoj 3157 & bzoj 3516 国王奇遇记 —— 推式子
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3157 https://www.lydsy.com/JudgeOnline/problem.p ...
随机推荐
- vi使用技巧(转载)
http://www.cnblogs.com/xusir/p/3245007.html 这是转载的链接
- 使用logrotate做nginx日志分割
版权申明:转载请注明出处. 文章来源:http://bigdataer.net/?p=266 背景 nginx是一款非常优秀的网络代理工具,但是其日志管理有点缺憾:nginx的access_log会无 ...
- 自己用java实现飞鸽传书 2 - 实现文件传输
第二步:实现文件传递. 上一步只是从服务端传递了一个字符串到客户端,这次需要对代码进行调整,实现从服务端获取文件,在客户端将文件存入目标地址. 调整后的代码: 服务端: import java.io. ...
- JSON01_资料
1. 资料网址: http://blog.csdn.net/vincent_czz/article/details/7333977 http://blog.csdn.net/huangwuyi/art ...
- 前端分布引导插件IntroJs的使用
在用户第一次使用网站的时候,一般会提供新手引导的提示,提示用户重要的功能使用,实现方法比较多,但是有一点,屏幕的自适应问题,大多数自己写的实现方法无非就是一个div遮罩层,然后再需要指引的位置放置一张 ...
- 使用ssm整合是创建Maven项目报错Failure to transfer com.thoughtworks.xstream:xstream:pom:1.3.1
Description Resource Path Location TypeFailure to transfer com.thoughtworks.xstream:xstream:pom:1.3. ...
- HTML5标签学习
<abbr> 表示一个缩写形式,比如 "Inc."."etc.".通过对缩写词语进行标记,您就能够为浏览器.拼写检查程序.翻译系统以及搜索引擎分度器 ...
- spring: @RequestMapping注解
处理GET/POST请求方法 1.常用的: import org.springframework.web.bind.annotation.RequestMapping; @Controller pub ...
- 一 web爬虫,requests请求
requests请求,就是用python的requests模块模拟浏览器请求,返回html源码 模拟浏览器请求有两种,一种是不需要用户登录或者验证的请求,一种是需要用户登录或者验证的请求 一.不需要用 ...
- idea结合git使用(正常项目开发过程中的使用)
1.如何将本地项目代码提交到公司码云上面 1.项目右键 2. 在远处的remotes里面设置你的url