题目链接
十万组的输入,让我想到了用复杂度较低的打表做法,打个表一切都好直接输出!
怎么处理?先看到这幅杨辉三角图:
1 (0)
1 1 (1)
1 2 1 (2)
1 3 3 1 (3)
1 4 6 4 1 (4)
1 5 10 10 5 1 (5)
.......
然后,我们首先看到其中图为对称的,所以到达c[d][u]的路径与c[d][d-u]是相等的,这样处理左半边就方便了(为了方便,我们只看半边即可)。
举个例子,c[5][1]就是左边的“5”那个点,我们找到它的最短路径不就是c[0][0]+c[1][0]+c[2][0]+c[3][0]+c[4][0]+c[5][1]=10,这个规律就不是很明显,但是,如果我们拆开来看,就是直接先走最左那一列(不看拐点),和为(5-1),剩下的是“c[5][1]”这个点还有拐点“1”;
那么在看c[5][2],就是“10”这个点,可以发现它的最左“1”(去拐点)的个数为(5-2),剩下的就是“c[5][2]”、“c[4][1]”、“c[3][0]”。
那么我们怎么总和这个规律,不难发现可以写成这样的表达式:
(n-k)+c[n-k][0]+c[n-k+1][1]+c[n-k+2][2]+......+c[n-k+i][i]+......+c[n][k]
用组合数的形式表示一下就是:
c(n-k,0)+c(n-k+1,1)+c(n-k+2,2)+...+c(n-k+i,i)+...+c(n,k)+n-k
看到c(n-k,0)与c(n-k+1,1)发现,它们底数差1,上面也差1,所以用到组合数学的知识:有c(n-k,0)=c(n-k+1, 0),然后根据c(n, k)+c(n, k+1)=c(n+1, k)的知识,将c(n-k,0)与c(n-k+1,1)组合得到c(n-k+2, 0);后根据数学归纳法得到最后的公式是:c(n+1,k)+n-k。
那么剩下的就是处理数据了,我用素数筛的方式先把所有素数筛出来,然后在打个表记录当取模对应素数的时候阶乘的值,以及对应阶乘与素数的时候阶乘的逆元的值,用以做最后的组合数计算。——那么就是完整的步骤了。
完整代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
using namespace std;
typedef long long ll;
const int maxN=1e4+1;
int N, K, P;
int jiecheng[maxN][1500];
int pr_prime[maxN], re_prime[maxN]; //位置、素数
int inv[maxN][1500]; //逆元
int fast_mi(int x, int y, int mod) //快速幂
{
int res=1;
x%=mod;
while(y)
{
if(y&1) res=(res*x)%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
void Jc() //预处理,先是素数筛,再是阶乘(对应模),最后是其阶乘的逆元
{
int prime_cnt=0; bool prime[maxN]; memset(prime, true, sizeof(prime)); //素数筛
for(int i=2; i<maxN; i++)
{
if(prime[i])
{
re_prime[++prime_cnt]=i; //第几个素数对应的该素数
pr_prime[i]=prime_cnt; //该素数是第几位
for(int j=i+i; j<maxN; j+=i)
{
prime[j]=false;
}
}
}
for(int i=1; i<=prime_cnt; i++) //取模的素数
{
jiecheng[0][i] = inv[0][i] = 1;
for(int j=1; j<re_prime[i]; j++) //j阶
{
jiecheng[j][i]= ( j * jiecheng[j-1][i] ) %re_prime[i]; //求阶乘,取模
inv[j][i]=fast_mi(jiecheng[j][i], re_prime[i]-2, re_prime[i]); //逆元
}
}
}
int Calc(int down, int up)
{
if(up>down) return 0;
else if(down == up) return 1;
else return ( (jiecheng[down][pr_prime[P]] * inv[up][pr_prime[P]]%P )*inv[down-up][pr_prime[P]] )%P;
}
int solve(int down, int up)
{
if(up==0) return 1; //递归在此终结,毕竟down是大于up的e初始时候,最后肯定也以up为终点
return (Calc(down%P, up%P)%P * solve(down/P, up/P) )%P;
}
int main()
{
Jc();
int Cas=0;
while(scanf("%d%d%d", &N, &K, &P)!=EOF)
{
if(2*K>N) K=N-K;
printf("Case #%d: %d\n", ++Cas, (solve(N+1, K)+N-K)%P);
}
return 0;
}
/*
c(n-k,0)+c(n-k+1,1)+c(n-k+2,2)+...+c(n-k+i,i)+...+c(n,k)+n-k
=c(n-k+1,0)+c(n-k+1,1)+c(n-k+2,2)+...+c(n-k+i,i)+...+c(n,k)+n-k
=c(n-k+2,1)+c(n-k+2,2)+...+c(n-k+i,i)+...+c(n,k)+n-k
=c(n-k+3,2)+...+c(n-k+i,i)+...+c(n,k)+n-k
=c(n-k+i,i-1)+...+c(n-k+i,i)+c(n,k)+n-k
=c(n,k-1)+c(n,k)+n-k
=c(n+1,k)+n-k
*/