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 ...
随机推荐
- 详解Linux系统中的文件名和文件种类以及文件权限
Linux文件种类与副文件名 一直强调一个概念,那就是:任何装置在Linux底下都是文件, 不仅如此,连资料沟通的介面也有专属的文件在负责-所以,你会瞭解到,Linux的文件种类真的很多- 除了前面提 ...
- C++中虚函数和纯虚函数的区别与总结
首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数没有被实现. 定义纯虚函数是为了实 ...
- Generate parentheses,生成括号对,递归,深度优先搜索。
问题描述:给n对括号,生成所有合理的括号对.比如n=2,(()),()() 算法思路:利用深度优先搜索的递归思想,对n进行深度优先搜索.边界条件是n==0:前面电话号组成字符串也是利用dfs. pub ...
- Object有哪些方法?
有9个方法 1 clone 2 toString() 3 getClass 4 finalize 5 equals 6 hascode 7 notify 8 notifall 9 wait
- 公共域名服务DNS 114.114.114.114和8.8.8.8
一.两者的联系 114.114.114.114和8.8.8.8,这两个IP地址都属于公共域名解析服务DNS其中的一部分,而且由于不是用于商业用途的,这两个DNS都很纯净,不用担心因ISP运营商导致的D ...
- DDOS SYN Flood攻击、DNS Query Flood, CC攻击简介——ddos攻击打死给钱。限网吧、黄网、博彩,,,好熟悉的感觉有木有
摘自:https://zhuanlan.zhihu.com/p/22953451 首先我们说说ddos攻击方式,记住一句话,这是一个世界级的难题并没有解决办法只能缓解 DDoS(Distributed ...
- Cassandra key说明——Cassandra 整体数据可以理解成一个巨大的嵌套的Map Map<RowKey, SortedMap<ColumnKey, ColumnValue>>
Cassandra之中一共包含下面5种Key: Primary Key Partition Key Composite Key Compound Key Clustering Key 首先,Prima ...
- 条款23:宁以non-member, non-friend,替换member函数。
考虑下面这种经常出现的使用方式: class webBroswer{ public: ... void clearCache(); void clearHistory(); void removeCo ...
- New Concept English three (38)
26w/m 45 Future historians will be in a unique position when they come to record the history of our ...
- Juint 单元测试(1)
Junit 是一个基于Java语言的回归单元测试框架.是白盒测试的一种技术,记住这些就可以了. 为项目添加Junit 1 右键项目名称选择“Properties”,在弹出的窗体中选择“Java Bui ...