https://vj.xtuacm.cf/contest/view.action?cid=59#problem/E
这题目用的线性方法求逆元要记住
题目的意思是说,在杨辉三角上找一条路径,每次必须往下走一行,使得这个路径上所有数字的总和最小,输出取模值。
这里对于组合数有一个变换。C(n-1,k-1)+C(n-1,k)=C(n,k)
同时,仔细观察杨辉三角就知道,对于每行,从左至中间都是递增的,同时又根据对称性,把所有大于每行中点的点对称到左边来。
这样从目标点出发,每次都往左上方走,然后,走到第0列的时候就往上一直走(全为1),即最小值。
这样其结果可以表示为:
C(n,k)+C(n-1,k-1)+……+C(n-k+1,1)+C(n-k,0)+C(n-k-1,0)+……C(0,0);
注意观察这个式子和我开始列出来的那个公式,如果我们把C(n-k,0)写成C(n-k+1,0)(为什么?因为都是1呗!),
这样,上面式子昨天那一段,可以从尾到头依次合并,最终合并为C(n+1,k),ORZ。所以这里只要求一次组合数就可以了呢。
但是问题又来了,题目明确说了有100000组数据,而一般用Lucas定理求组合数的时间复杂度和P同级。这里P<=10000。所以这样做就会超时呢。
定义一个数组,所有小于10000的数的阶乘对所有小于10000的的素数进行取模,这样到lucas定理计算的时候就可以直接用。同时对逆元进行打表处理。
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
int mod;
int prime[1500],shu[10005],cnt=0;//质数打表
int fac[10005][10005],inv[10005][10005];//阶乘打表,逆元打表
void init1()//质数打表
{
for(int i=2;i<=10000;i++)
{
if(!shu[i])
{
prime[cnt++]=i;
for(int j=i*2;j<=10000;j+=i)
shu[j]=1;
}
}
}
int quick_pow(int n,int m,int k)
{
int ans=1;
while(m)
{
if(m&1)ans=(ans*n)%k;
m>>=1;
n=(n*n)%k;
}
return ans;
}
void init2()//预处理阶乘表和逆元表
{
for(int i=0;i<cnt;i++)
{
fac[prime[i]][0]=inv[prime[i]][0]=1;
for(int j=1;j<=prime[i];j++)
{
fac[prime[i]][j]=(fac[prime[i]][j-1]*j)%prime[i];
inv[prime[i]][j]=quick_pow(fac[prime[i]][j],prime[i]-2,prime[i]);
//费马小定理求逆元
}
}
}
int C(int m,int n)//组合数
{
if(n>m)return 0;
return fac[mod][m]*(inv[mod][n]*inv[mod][m-n]%mod)%mod;
}
int lucas(int m,int n)//lucas定理
{
if(!n)return 1;
else
return C(m%mod,n%mod)*lucas(m/mod,n/mod);
}
int main()
{
int k,n,p;
int ca=1;
init1();
init2();
while(~scanf("%d%d%d",&n,&k,&p))
{
mod=p;
if(k>n/2)
k=n-k;
int s=(lucas(n+1,k)+n-k)%mod;
printf("Case #%d: %d\n",ca++,s);
}
return 0;
}