HDU-4373 Mysterious For(中国剩余定理+卢卡斯定理+可重组合)

题意

定义两种类型的 for f o r 循环:

for(int a[i]=0;a[i]<n;a[i]++){   //类型一
    ...
}
for(int a[i]=a[i-1];a[i]<n;a[i]++){   //类型二
    ...
}

现在给定循环范围 n n 并将 m 重循环嵌套,最里层的循环内是 f f 函数,已知类型一的循环有 k 个并给定这 k k 个循环的位置。求 f 函数调用了多少次,次数模以 364875103 364875103
1n106 1 ≤ n ≤ 10 6
1m105 1 ≤ m ≤ 10 5
1k15 1 ≤ k ≤ 15

思路

首先对 364875103 364875103 进行质因数分解得到 97×3761599 97 × 3761599 ,分别对这两个质数求模再用 CRT C R T 合并即可。
把一个类型一循环和下面与它连续的类型二循环看成一个整体。这里的循环变量从上到下满足单调不减,循环的次数事实上是一个可重组合,可重组合的 n n 就是第一层循环的长度 n ,可重组合的 m m 是这个整体的循环语句数。
对于一个可重的组合 H ,有 Hmn=Cmn+m1 H n m = C n + m − 1 m
证明可采用构造法,设选的 m m 个数为 a1,a2,a3...am(ai<=ai+1,ai[1,n]) ,再构造一个 b b 数组满足 bi=ai+i1 ,这个用 a a 数组 表示 b 数组得 a1,a2+1,a3+2...am+m1 a 1 , a 2 + 1 , a 3 + 2... a m + m − 1 ,不难发现 ai<ai+1ai[1,n+m1] a i < a i + 1 , a i ∈ [ 1 , n + m − 1 ] ,这满足一个普通的组合 Cmn+m1 C n + m − 1 m ,证毕。
而计算 Cmn%P C n m % P 时常常因为要进行阶乘的数大于 P P 导致阶乘均为 0 ,从而无法得出正确答案。这是 Lucas L u c a s 定理的好处就体现出来了。它能让计算时 n,m n , m 均小于 P P <script type="math/tex" id="MathJax-Element-91">P</script> ,防止出现以上的情况。

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define FOR(i,x,y) for(int i=(x);i<=(y);i++)
#define DOR(i,x,y) for(int i=(x);i>=(y);i--)
typedef long long LL;
using namespace std;
int _P[2]={97,3761599};
LL fac[2][3800003];

LL Pow(LL a,LL p,LL P)
{
    LL res=1;
    while(p)
    {
        if(p&1)(res*=a)%=P;
        (a*=a)%=P;
        p>>=1; 
    }
    return res;
}
void init()
{
    FOR(i,0,1)
    {
        LL f=1;
        FOR(j,0,_P[i]-1)
        {
            fac[i][j]=f;
            (f*=j+1)%=_P[i];
        }
    }
    return;
}

struct CRT
{
    LL A,P;
    LL gcd(LL a,LL b){return b?gcd(b,a%b):a;}
    void exgcd(LL a,LL b,LL &x,LL &y)
    {
        if(!b){x=1,y=0;return;}
        exgcd(b,a%b,y,x);y-=a/b*x;
        return;
    }
    LL Exgcd(LL A,LL B,LL C)
    {
        LL k1,k2;
        exgcd(A,B,k1,k2);
        return (k1*C%B+B)%B; 
    }
    void kase_insert(CRT _)
    {
        LL res=Exgcd(P,_.P,_.A-A);
        A+=res*P;
        P=P/gcd(P,_.P)*_.P;
        return;
    }
};

LL inv(LL a,LL P){return Pow(a,P-2,P);}
LL C(LL n,LL m,int i){return m>n?0:fac[i][n]*inv(fac[i][m]*fac[i][n-m]%_P[i],_P[i])%_P[i];}
LL Lucas(LL n,LL m,int i){return m?Lucas(n/_P[i],m/_P[i],i)*C(n%_P[i],m%_P[i],i)%_P[i]:1;}
LL rep_C(LL n,LL m,int i){return Lucas(n+m-1,m,i);}

int main()
{
    init();
    int T;
    scanf("%d",&T);
    FOR(Ti,1,T)
    {
        int n,m,k,a[18];
        scanf("%d%d%d",&n,&m,&k);
        FOR(i,1,k)scanf("%d",&a[i]);
        a[k+1]=m;
        CRT sum[2];
        FOR(i,0,1)sum[i].P=_P[i],sum[i].A=1;
        FOR(i,0,1)FOR(j,1,k)(sum[i].A*=rep_C(n,a[j+1]-a[j],i))%=_P[i];
        sum[0].kase_insert(sum[1]);
        printf("Case #%d: %lld\n",Ti,sum[0].A);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值