DP? 【HDU - 3944】【素数筛+阶乘和阶乘的逆元的预处理】

题目链接

十万组的输入,让我想到了用复杂度较低的打表做法,打个表一切都好直接输出!

怎么处理?先看到这幅杨辉三角图:

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
 */

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wuliwuliii

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值