HDU 4373 Mysterious For(Lucas定理、中国剩余定理)

题目链接:
HDU 4373 Mysterious For
题意:
定义两种循环:
for(int a[i]=0;a[i]<n;++a[i])...
for(int a[i]=a[i1];a[i]<n;++a[i])...
会有 num 层循环属于第一种循环,给出这些循环的位置,求总共的循环次数是多少?答案 mod 364875103
分析:
首先我们考虑第一种循环下面跟 k1 个第二种循环,总共是 k 层循环的情况。定义在t层循环到 i 的次数为ft(i)

  • 对于第一层循环,循环到 i ,显然有
    f1(i)=1f1(i)=n=C1n
  • 对于第二层循环,因为第二层循环属于第二种循环,想要循环到 i ,那么相应的第一层循环就要i,也就是
    f2(i)=jif1(j)=i:f2(i)=(n+1)n2=C2n+1
  • 对于第三层循环,同理可得:
    f3(i)=jif2(i)=(i+1)i2f3(i)=12(i2+i)=(n+2)(n+1)(n)6=C3n+2
  • 可以发现对于第 k 层循环总的循环次数是
    Ckn+k1

接着我们需要计算 Ckn+k1 %364875103 .比较坑爹的是 364875103 不是一个素数!但是对 364875103 进行素数检查时可以发现 364875103=973761599 而这两个因子都是素数,令 mod1=97,mod2=3761599,mod=364875103
m=k,n=n+k1 ,我们

Cmn  %  modx,Cmn % mod1x1,Cmn % mod2x2

则有:
x x1(%  mod1)x x2(%  mod2)

因为 mod1,mod2 都是素数,所以 x1x2 都可以用 Lucas 那么剩下的问题就是求解上面的 同余方程了。
利用 中国剩余定理求解同余方程。
我们定义:
inv1:(mod2mod1)mod2inv2:(mod1mod2)mod1

那么答案就是:
x=(inv1x1 +inv2x2)% mod

最后在根据乘法原理易知每段求得的答案需要累乘。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <climits>
#include <cmath>
#include <ctime>
#include <cassert>
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
using namespace std;
typedef long long ll;
const int mod1 = 97;
const int mod2 = 3761599;
const ll MOD = 364875103;
const int MAX_M = 100010;

ll fac1[mod1 + 10], fac2[mod2 + 10], e1, e2;

ll quick_pow(ll a, ll b, ll mod)
{
    ll res = 1, tmp = a;
    while(b) {
        if(b & 1) res = res * tmp % mod;
        tmp = tmp * tmp % mod;
        b >>= 1;
    }
    return res;
}

void init() 
{
    fac1[0] = fac2[0] = 1;
    for(int i = 1; i < mod1; ++i) { fac1[i] = fac1[i - 1] * i % mod1; }
    for(int i = 1; i < mod2; ++i) { fac2[i] = fac2[i - 1] * i % mod2; }
    e1 = quick_pow(mod2, mod1 - 2, mod1) * mod2;
    e2 = quick_pow(mod1, mod2 - 2, mod2) * mod1;
}

ll ex_gcd(ll a, ll b, ll& x, ll& y) 
{
    if(b == 0) {
        x = 1, y = 0;
        return a;
    }
    ll r = ex_gcd(b, a % b, y, x);
    y -= a / b * x;
    return r;
}

ll inv(ll a, ll n)
{
    ll x, y;
    ll d = ex_gcd(a, n, x, y);
    if(d == 1) return (x % n + n) % n;
    else return 0;
}

ll C(int n, int m, int id, int mod)
{
    if(m > n) return 0;
    ll a, b, c;
    if(id == 1) {
        a = fac1[n], b = fac1[n - m], c = fac1[m];
    } else {
        a = fac2[n], b = fac2[n - m], c = fac2[m];
    }
    //return a * inv(b * c % mod, mod) % mod;  //也可以用扩展欧几里德求逆元
    return a * quick_pow(b * c % mod, mod - 2, mod) % mod;
}

ll Lucas(int n, int m, ll mod, int id)
{
    if(m == 0) return 1;
    return C(n % mod, m % mod, id, mod) * Lucas(n / mod, m / mod, mod, id) % mod; 
}

int main()
{
    init();
    int T, n, m, num, data[30], cases = 0;
    scanf("%d", &T);
    while(T--) {
        scanf("%d%d%d", &n, &m, &num);
        for(int i = 0; i < num; ++i) { scanf ("%d", &data[i]); }
        data[num++] = m;
        ll ans = 1;
        for(int i = 1; i < num; ++i) {
            int k = data[i] - data[i - 1];
            ll t1 = Lucas(n + k - 1, k, mod1, 1) * e1 % MOD;
            ll t2 = Lucas(n + k - 1, k, mod2, 2) * e2 % MOD;
            ans = ans * (t1 + t2) % MOD;
        }
        printf("Case #%d: %lld\n", ++cases, ans);
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值