luogu4345 [SHOI2015]超能粒子炮·改(组合数/Lucas定理)

link

输入\(n,k\),求\(\sum_{i=0}^k{n\choose i}\)对2333取模,10万组询问,n,k<=1e18

注意到一个2333这个数字很小并且还是质数这一良好性质,我们可以根据Lucas定理优化式子

为了方便,令\(p=2333\)

\(f(n,k)=\sum_{i=0}^k{n\choose i}\)

对于\(i\in[0,p\lfloor\frac kp\rfloor)\),根据lucas定理有\({n\choose i}={n\%p\choose i\%p}{n/p\choose i/p}\)

对于每一对\((i\%p,i/p)\)都能惟一确定一个\(i\),根据乘法原理有

\(f(n\%p,p-1)*f(\lfloor\frac np\rfloor,\lfloor\frac kp\rfloor-1)\)

对于\(i\in[p\lfloor\frac kp\rfloor,k]\)则它们/p的值相同,根据Lucas定理可以化为\({\lfloor\frac np\rfloor\choose\lfloor\frac kp\rfloor}*f(n\%p,k\%p)\)

所以\(f(n,k)=f(n\%p,p-1)*f(\lfloor\frac np\rfloor,\lfloor\frac kp\rfloor-1)+{\lfloor\frac np\rfloor\choose\lfloor\frac kp\rfloor}*f(n\%p,k\%p)\)

先预处理0~p-1阶乘及其逆元,0~p-1里的组合数可以O(1)

注意到在f的递推式中频繁用到了0~p-1内的f值,所以先O(p^2)处理这些f

那么时间复杂度递推式就是\(T(n,k)=T(\lfloor\frac np\rfloor,\lfloor\frac kp\rfloor)+\log p\),如果nk同阶,复杂度\(O(T\log n\log ^2p)\)好像是

一开始复杂度写错了,最后6个点狂T。。。

代码

#include <cstdio>
using namespace std;

const int p = 2333;

int fac[3000], inv[3000];
int f[3000][3000];

int qpow(int x, int y)
{
    int res = 1;
    for (x %= p; y > 0; x = x * x % p, y >>= 1) if (y & 1) res = res * x % p;
    return res;
}

int c(long long n, long long m)
{
    if (n < m || m < 0) return 0;
    if (n < p && m < p) return fac[n] * inv[m] % p * inv[n - m] % p;
    return c(n / p, m / p) * c(n % p, m % p) % p;
}

int work(long long n, long long k)
{
    if (n < p && k < p) return f[n][k];
    return (c(n / p, k / p) * work(n % p, k % p) + work(n % p, p - 1) * work(n / p, k / p - 1)) % p;
}

int main()
{
    fac[0] = 1;
    for (int i = 1; i < p; i++)
        fac[i] = fac[i - 1] * i % p;
    inv[p - 1] = qpow(fac[p - 1], p - 2);
    for (int i = p - 1; i >= 1; i--)
        inv[i - 1] = inv[i] * i % p;
    for (int i = 0; i < p; i++)
    {
        f[i][0] = c(i, 0);
        for (int j = 1; j < p; j++)
            f[i][j] = (f[i][j - 1] + c(i, j)) % p;
    }
    int t; scanf("%d", &t);
    while (t --> 0)
    {
        long long x, y;
        scanf("%lld%lld", &x, &y);
        printf("%d\n", work(x, y));
    }
    return 0;
}

WA了好几发,define int long long过了后来发现计算C时候三个数乘一起就炸int了。。。

转载于:https://www.cnblogs.com/oier/p/10302092.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值