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 ...
随机推荐
- iOS日常学习 - App之间常用的五种通信方式及适用场景总结
本文为转载学习,原文地址 iOS系统是相对封闭的系统,App各自在各自的沙盒(sandbox)中运行,每个App都只能读取iPhone上iOS系统为该应用程序程序创建的文件夹AppData下的内容,不 ...
- HttpServletResponse 的 sendError( )方法以及常用的HttpServletResponse常量级错误代码
HttpServletResponse 的 sendError( )方法以及常用的HttpServletResponse常量级错误代码 转载:http://hi.baidu.com/yanfei_ ...
- 怎样发布一个工程到自己的GitHub
本人小白一枚: 第一次通过命令行的形式将自己的工程发布到自己的GitHub上去,还是请教了大神之后才完成的.通过总结经验,小白也掌握了一些关于往代码托管平台上发布工程的方式,其实只要用心学,里面的东西 ...
- StrStr,判断一个字符串是不是另一个字符串的字串,并返回子串的位置
public int strStr(String haystack, String needle) { if(haystack == null || needle == null) { return ...
- LeetCode第[46]题(Java):Permutations(求所有全排列) 含扩展——第[47]题Permutations 2
题目:求所有全排列 难度:Medium 题目内容: Given a collection of distinct integers, return all possible permutations. ...
- 关于jQuery中的offset()和position()
在jQuery中有两个获取元素位置的方法offset()和position().position()方法是在1.2.6版本之后加入的,为什么要引 入这个方法呢?这两个方法之间有什么异同?使用的时候应该 ...
- AspectJ的Execution表达式
在使用spring框架配置AOP的时候,不管是通过XML配置文件还是注解的方式都需要定义pointcut"切入点" 例如定义切入点表达式 execution (* com.sam ...
- c语言中的string
1. strlen(char const* s); 函数传入的是c风格字符串(即以‘\0’结尾的字符数组),返回的长度为size_t(即unsigned int),其长度不包括'\0'. 2. str ...
- CC 攻击检测研究现状
网络层ddos 是让去往银行的道路交通变得拥堵,无法使正真要去银行的人到达:常利用协议为网络层的,如tcp(利用三次握手的响应等待及电脑tcp 连接数限制)等应用层ddos 则是在到达银行后通过增办. ...
- day5-shutil模块
一.概述 我们通过python操作文件时,除正常读写操作外,有时还需要进行拷贝.删除.打包等操作,虽然os模块提供了部分功能,但还是不够完善,这里要讲讲专业的高级的文件,文件夹,压缩包处理模块shut ...