HDU 6333 分块 | 莫队算法

本文介绍了一种针对组合数学中特定求和问题的高效算法。通过对杨辉三角性质的应用,推导出了快速更新组合数求和的方法,并提供了两种实现方案——莫队算法和分块算法,最终达到O(n√n)的时间复杂度。

题目链接


题意:
给定 n,m n , m ,求出 mi=0Cin ∑ i = 0 m C n i ,一共有 1e5 1 e 5 组询问。


思路:

F[n][m]=i=0mCin F [ n ] [ m ] = ∑ i = 0 m C n i

则结合杨辉三角,可得:

F[n+1][m]=2F[n][m]Cmn F [ n + 1 ] [ m ] = 2 ∗ F [ n ] [ m ] − C n m

F[n1][m]=(F[n][m]+Cmn1)/2 F [ n − 1 ] [ m ] = ( F [ n ] [ m ] + C n − 1 m ) / 2

F[n][m+1]=F[n][m]+Cm+1n F [ n ] [ m + 1 ] = F [ n ] [ m ] + C n m + 1

F[n][m1]=F[n][m]Cmn F [ n ] [ m − 1 ] = F [ n ] [ m ] − C n m

故可以直接使用莫队算法来解决该题,时间复杂度 O(nn) O ( n n )


也可以直接分块,因为 n,m1e5 n , m ≤ 1 e 5 ,故所有的 F F 共有1e10个数,因为每一个维度的 +1/1 + 1 / − 1 均可以 O(1) O ( 1 ) 推出,所以可以直接对 n n 分块,假定块的大小为nuit=500,则时间复杂度为: O(nm/nuit) O ( n ∗ m / n u i t )

此题得解。


代码:
莫队算法:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;

const int mod = 1e9 + 7;
const int A = 1e5 + 10;
int fac[A], Inv[A], Ans[A], block, T;
ll res;

class Que{
public:
    int L, R, id;

    bool operator<(const Que& rhs) const{
        if(L/block == rhs.L/block) return R < rhs.R;
        return L/block < rhs.L/block;
    }
}Q[A];

int Mul(ll a, int b){
    a *= b;
    return a >= mod?a%mod:a;
}

int Add(int a, int b){
    a += b;
    return a >= mod?a-mod:a;
}

int Sub(int a, int b){
    a -= b;
    return a < 0?a+mod:a;
}

int fast_mod(int n, int m){
    int res = 1;
    while(m > 0){
        if(m&1) res = Mul(res, n);
        n = Mul(n, n);
        m >>= 1;
    }
    return res;
}

int Comb(int n, int m){
    return Mul(Mul(fac[n], Inv[m]), Inv[n-m]);
}

void Init(){
    fac[0] = Inv[0] = 1;
    for (int i = 1; i < A; i++) fac[i] = Mul(fac[i-1],i);
    Inv[A-1] = fast_mod(fac[A-1], mod - 2);
    for (int i = A-2; i>=1; i--) Inv[i] = Mul(Inv[i+1], i + 1);
}

void solve(){
    int l = 1, r = 0;
    res = 1;
    for (int i = 1; i <= T; i++) {
        while (r < Q[i].R) {
            r++;
            res = Sub(Mul(res, 2), Comb(r - 1, l));
        }
        while (l > Q[i].L) {
            res = Sub(res, Comb(r, l));
            l--;
        }
        while (r > Q[i].R) {
            res = Mul(Add(res, Comb(r - 1, l)), Inv[2]);
            r--;
        }
        while (l < Q[i].L) {
            l++;
            res = Add(res, Comb(r, l));
        }
        Ans[Q[i].id] = res;
    }
    for (int i = 1; i <= T; i++) printf("%d\n", Ans[i]);
}

int main(){
    Init();
    scanf("%d",&T);
    block = 0;
    for (int i = 1; i <= T; i++) {
        Q[i].id = i;
        scanf("%d%d", &Q[i].R, &Q[i].L);
        block = max(block, Q[i].R);
    }
    block = (int)sqrt(1.0 * block);
    sort(Q + 1, Q + 1 + T);

    solve();
    return 0;
}

n n 分块:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<string>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
typedef long long ll;

const int mod = 1e9 + 7;
const int A = 1e5 + 1000;
const int B = A/500 + 100;
int fac[A], Inv[A];
int a[B][A], tem[2][A];

int Mul(ll a, int b){
    a *= b;
    return a >= mod?a%mod:a;
}

int Add(int a, int b){
    a += b;
    return a >= mod?a-mod:a;
}

int Sub(int a, int b){
    a -= b;
    return a < 0?a+mod:a;
}

int fast_mod(int n, int m){
    int res = 1;
    while(m > 0){
        if(m&1) res = Mul(res, n);
        n = Mul(n, n);
        m >>= 1;
    }
    return res;
}

int Comb(int n, int m){
    return Mul(Mul(fac[n], Inv[m]), Inv[n-m]);
}

void Init(){
    fac[0] = Inv[0] = 1;
    for (int i = 1; i < A; i++) fac[i] = Mul(fac[i-1],i);
    Inv[A-1] = fast_mod(fac[A-1], mod - 2);
    for (int i = A-2; i>=1; i--) Inv[i] = Mul(Inv[i+1], i + 1);

    for (int i = 0; i < A; i += 500) {
        a[i/500][0] = 1;
        for (int j = 1; j <= i; j++) a[i/500][j] = Add(a[i/500][j-1], Comb(i,j));
    }
}

int main(){
    Init();
    int T, n, m, res;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d", &n, &m);
        n++;
        res = 0;
        res = a[(n/500)+1][m];
        for (int i = ((n/500)+1)*500; i >= n; i--){
            res = Mul(Add(res,Comb(i-1,m)), Inv[2]);
        }
        printf("%d\n", res);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值