[AGC001E] BBQ Hard题解

108118239_p0

Problem

[AGC001E] BBQ Hard

计算:

i=1nj=i+1n(ai+bi+aj+bjai+aj)

其中n2×105ai,bi2000

Solution

aiai+bi则等价于求

i=1nj=i+1n(ai+ajbi+bj)

考虑使得式子变得更加对称,我们可以不限制i,j的相对大小,之后减去i=j的情况再除以2即可。

i=1nj=i+1n(ai+ajbi+bj)=12(i=1nj=1n(ai+ajbi+bj)i=1n(2ai2bi))

问题转化为求

i=1nj=1n(ai+ajbi+bj)

代数法

推导过程

考虑将i,j拆开分别计算贡献,可以联想到Vandermonde卷积
于是原式转换为

i=1nj=1n(ai+ajbi+bj)=i=1nj=1nk(aibik)(ajbj+k)=ki=1nj=1n(aibik)(ajbj+k)=k(i=1n(aibik))(j=1n(ajbj+k))=kF(k)F(k)

其中F(k)=i=1n(aibi+k)

然后我们容易想到一个O(na)的做法,那就是直接计算F(k),注意由于k的范围是[2000,2000],所以我们需要将k平移至[0,4000],这样就可以用一个数组来存储F(k)了。

for (int k = mink; k <= maxk; ++k) {
    for (int i = 1; i <= n; ++i) {
        if (k + b[i] >= 0 && k + b[i] <= a[i]) {
            F[k + SHIFT] = (F[k + SHIFT] + C[a[i]][k + b[i]]) % MOD;
        }
    }
}

优化

这个也许卡卡常就能过了,但是我们尝试去追求更好的时间复杂度
事实上,我们利用生成函数的相关知识,有

F(k)=i=1n(aibi+k)=i=1n(1+x)ai[xbi+k]=[xk]i=1n(1+x)aixbi=[xk+SHIFT]i=1n(1+x)aixbi+SHIFT=[xk+SHIFT]A(1+x)Ai,ai=Axbi+SHIFT

其中[xi]表示xi项的系数。
则我们只需要求出

A(1+x)Ai,ai=Axbi+SHIFT

这个式子可以用秦九韶算法来求,复杂度降低至O(a2)

for (int i = 1; i <= n; ++i) {
    bInA[a[i]].push_back(-b[i] + SHIFT);
}
for (int i = maxa; i >= 0; --i) {
    for (int k = 2* SHIFT; k >= 1; --k) {
        F[k] = (F[k] + F[k - 1]) % MOD;
    }
    for (auto bInAi : bInA[i]) {
        F[bInAi] = (F[bInAi] + 1) % MOD;
    }
}

完整代码

#include<iostream>
#include<vector>
using namespace std;
const int MAX_N = 200005, MOD = 998244353, MAX_A = 4000 + 5, SHIFT = 2000;
int ans, n, a[MAX_N], b[MAX_N], maxa, C[MAX_A][MAX_A], mink, maxk;
int fac[MAX_A * 2], inv[MAX_A * 2], invfac[MAX_A * 2];
int F[MAX_A];//from -2000 to 2000, 加上基数2000
int binom(int n, int k) {
    if (n < k) {
        return 0;
    }
    return 1ll * fac[n] * invfac[k] % MOD * invfac[n - k] % MOD;
}
vector<int> bInA[MAX_A];
signed main() {
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i] >> b[i];
        a[i] += b[i];
        maxa = max(maxa, a[i]);
        mink = min(mink, -b[i]);
        maxk = max(maxk, a[i] - b[i]);
    }
    for (int i = 0; i <= maxa; ++i) {
        C[i][0] = 1;
        for (int j = 1; j <= i; ++j) {
            C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;
        }
    }
    /*for (int k = mink; k <= maxk; ++k) {
        for (int i = 1; i <= n; ++i) {
            if (k + b[i] >= 0 && k + b[i] <= a[i]) {
                F[k + SHIFT] = (F[k + SHIFT] + C[a[i]][k + b[i]]) % MOD;
            }
        }
    }*/
    for (int i = 1; i <= n; ++i) {
        bInA[a[i]].push_back(-b[i] + SHIFT);
    }
    for (int i = maxa; i >= 0; --i) {
        for (int k = 2* SHIFT; k >= 1; --k) {
            F[k] = (F[k] + F[k - 1]) % MOD;
        }
        for (auto bInAi : bInA[i]) {
            F[bInAi] = (F[bInAi] + 1) % MOD;
        }
    }

    for (int k = mink; k <= maxk; ++k) {
        ans = (ans + 1ll * F[k + SHIFT] * F[-k + SHIFT] % MOD) % MOD;
    }
    fac[0] = inv[0] = invfac[0] = 1;
    fac[1] = inv[1] = invfac[1] = 1;
    for (int i = 2; i <= maxa * 2; ++i) {
        fac[i] = 1ll * fac[i - 1] * i % MOD;
        inv[i] = 1ll * (MOD - MOD / i) * inv[MOD % i] % MOD;
        invfac[i] = 1ll * invfac[i - 1] * inv[i] % MOD;
    }
    for (int i = 1; i <= n; ++i) {
        ans = (ans - binom(2 * a[i], 2 * b[i]) + MOD) % MOD;
    }
    ans = 1ll * ans * inv[2] % MOD;
    cout << ans << endl;
    return 0;
}

组合意义法

我们可以把这个值看做在网格图上的一点(a[i],b[i])走到(a[j],b[j])的方案数。
而网格图走的方案数可以直接递推得到。
那么我们对于每个点把它的坐标取反到第三象限,然后对于整个坐标系计算走到每一个格子的总方案。

递推式与网格路径完全相同

f[i][j] = (1ll * f[i][j] + f[i - 1][j] + f[i][j - 1]) % MOD;

需要注意的是初始条件

for(int i = 1; i <= n; i++){
    f[SHIFT - a[i]][SHIFT - b[i]]++;
} 
posted @   520Enterprise  阅读(96)  评论(2编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示