bzoj 4591: [Shoi2015]超能粒子炮·改 (Lucas定理)

题目描述

传送门

题目大意:给出两个整数 n,kn,k<=1018

C(n,0)+C(n,1)+C(n,2)+......C(n,k)  mod 2333

题解

一道不错的Lucas定理的题。首先这道题有一个很重要的特点,就是模数非常的小。
我们可以考虑对式子进行一些改动,以n=13,k=7,模数是3为例
C(13,0)+C(13,1)+C(13,2)+C(13,3)+C(13,4)+C(13,5)+C(13,6)+C(13,7)
利用Lucas定理,得
C(4,0)C(1,0)+C(4,0)C(1,1)+C(4,0)C(1,2)+C(4,1)C(1,0)+C(4,1)C(1,1)+C(4,1)C(1,2)+C(4,2)C(1,0)+C(4,2)C(1,1)
通过观察,式子可以变成

(i=0p1C(n%p,i)i=0k/p1C(n/p,i))+i=kppkC(n,i)

k/p1i=0C(n/p,i) 可以递归求解。然后剩下的两部分都可以预处理前缀和,快速的求解。

代码

#include
#include
#include
#include
#include
#define LL long long 
#define p 2333
using namespace std;
LL n,k;
int T,jc[3000],inv[3000],c[3000][3000],sum[3000][3000];
int quickpow(int num,int x)
{
    int base=num%p; int ans=1;
    while (x) {
        if (x&1) ans=ans*base%p;
        x>>=1;
        base=base*base%p;
    }
    return ans;
}
int C(LL n,LL m)
{
    if (m>n) return 0;
    return jc[n]*quickpow(jc[m],p-2)%p*quickpow(jc[n-m],p-2)%p;
}
int calc(LL n,LL m)
{
    if (m==0) return 1;
    return calc(n/p,m/p)*c[n%p][m%p]%p;
}
int solve(LL n,LL m)
{
    if (m<0) return 0;
    if (m==0) return 1;
    LL y=m/p;
    int ans=0; ans=sum[n%p][m%p]*calc(n/p,m/p)%p;
    int t1=solve(n/p,m/p-1);
    if (y) t1=t1*sum[n%p][p-1];
    return (t1+ans)%p;
}
int main()
{
    freopen("a.in","r",stdin);
    freopen("my.out","w",stdout);
    scanf("%d",&T);
    jc[0]=1;
    for (int i=1;i<=p;i++) jc[i]=jc[i-1]*i%p;
    for (int i=0;i<=p;i++) inv[i]=quickpow(jc[i],p-2);
    for (int i=0;i<=p;i++) c[i][0]=1;
    for (int i=1;i<=p;i++) 
     for (int j=1;j<=i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%p;
    for (int i=0;i<=p;i++) {
     sum[i][0]=c[i][0];
     for (int j=1;j<=p-1;j++) sum[i][j]=(sum[i][j-1]+c[i][j])%p;
    }
    while (T--) {
        scanf("%lld%lld",&n,&k);
        int ans=solve(n,k);
        printf("%d\n",ans);
    }
}

你可能感兴趣的:(数论,组合数取模,组合数学)