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 ...
随机推荐
- Batch Normalization 详解
一.背景意义 本篇博文主要讲解2015年深度学习领域,非常值得学习的一篇文献:<Batch Normalization: Accelerating Deep Network Training b ...
- 解析Linux系统的平均负载概念
一.什么是系统平均负载(Load average)? 在Linux系统中,uptime.w.top等命令都会有系统平均负载load average的输出,那么什么是系统平均负载呢?系统平均负载被定义为 ...
- Android高手进阶教程(十七)之---Android中Intent传递对象的两种方法(Serializable,Parcelable)!
[转][原文] 大家好,好久不见,今天要给大家讲一下Android中Intent中如何传递对象,就我目前所知道的有两种方法,一种是Bundle.putSerializable(Key,Object); ...
- Logstash过滤器修改数据
数据修改(Mutate) filters/mutate 插件是 Logstash 另一个重要插件.它提供了丰富的基础类型数据处理能力.包括类型转换,字符串处理和字段处理等. 类型转换 类型转换是 fi ...
- OWIN初探
什么是 OWIN ? OWIN 的全称是 "Open Web Interface for .NET", OWIN 在 .NET Web 服务器和 .NET Web 应用之间定义了一 ...
- css3 放大缩小代码
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- 如何让pycharm以py.test方式运行
第一步:进入File—Settings—Python Integrated Tools 发现设置中Default test runner是Unittests 将其改为py.test,点击OK保存 如果 ...
- 四十五 Python分布式爬虫打造搜索引擎Scrapy精讲—elasticsearch(搜索引擎)的bool组合查询
bool查询说明 filter:[],字段的过滤,不参与打分must:[],如果有多个查询,都必须满足[并且]should:[],如果有多个查询,满足一个或者多个都匹配[或者]must_not:[], ...
- 【scala】构造器
和Java或C++一样,Scala可以有任意多的构造器. 不过Scala类有一个构造器比其他所有构造器都更为重要,它就是主构造器. 除了主构造器之外,类还可以有任意多的辅助构造器. 主构造器 在Sca ...
- BZOJ3528: [Zjoi2014]星系调查
唉,看到这题直接想起自己的Day1,还是挺难受的,挺傻一题考试的时候怎么就没弄出来呢…… 这两天CP让我给他写个题解,弄了不是很久就把这个题给弄出来了,真不知道考试的时候在干嘛. 明天就出发去北京了, ...