专栏文章

题解:P13349 「ZYZ 2025」自然数序列

P13349题解参与者 2已保存评论 1

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
1 条
当前快照
1 份
快照标识符
@miotj7y4
此快照首次捕获于
2025/12/03 00:54
3 个月前
此快照最后确认于
2025/12/03 00:54
3 个月前
查看原文

P13349 完全背包题解

题意简述

给定长度为 nn正整数序列 aa,进行 qq 次询问。每次询问给定 l,r,kl, r, kkk 个限制条件,求满足 li=1naibirl \leq \sum_{i=1}^{n} a_i b_i \leq r 和所有限制条件的自然数序列 bb 的数量。结果对 998244353998244353 取模。

思路

一道背包 DP 题,核心是计算在 kk 个位置固定的前提下,序列 bb 的加权和落在 [l,r][l, r] 的方案数。我们可以 DP 预处理全局背包,再通过退背包处理 kk 个限制条件,最后计算区间和。定义 gsg_s 为完全背包计算所有物品任意取值时,加权和为 ss 的方案数,转移为 gj=(gj+gjai)mod998244353g_j=(g_j+g_{j-a_i}) \bmod 998244353,其中 aia_i 为物品重量。

对于每个询问:

  1. 根据 kk 个限制条件,累加固定位置的贡献 s=axys = \sum a_x \cdot y。若 s>rs > r,则直接输出 00
  2. 自由部分需满足 [max(0,ls),rs][\max(0, l-s), r-s],若区间无效则直接输出 00
  3. 将固定位置对应的物品从全局背包中移除(按重量降序进行逆背包操作)。
  4. 最后,在 [max(0,ls),rs][\max(0, l-s), r-s] 区间内累加方案数。
具体详见代码注释。

特别注意

本题时限改为 500ms 后此代码有点卡常,开 O2、关同步流、endl'\n' 之后多试两次能过(亲测)(数据最强的点跑到了 498ms,如果卡不过可以手写快读快写)。

Code

码风良好,放心食用!
CPP
#include <bits/stdc++.h>
using namespace std;

const int N = 5000;         // 背包最大值
const int MOD = 998244353;
int n, q;
int a[5005];
int g[5005];                // 全局背包数组

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cin >> n >> q;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    // 初始化全局背包:完全背包DP
    memset(g, 0, sizeof(g)); // 清空背包数组
    g[0] = 1;                // 加权和为 0 的方案数为 1(全取 0)
    for (int i = 0; i < n; i++) {
        int av = a[i];       // 当前物品重量
        // 完全背包状态转移
        for (int j = av; j <= N; j++) {
            g[j] = (g[j] + g[j - av]) % MOD; // 累加方案数
        }
    }
    // 处理每组询问
    while (q--) {
        long long l, r;      // 区间边界
        int k;               // 限制条件数量
        cin >> l >> r >> k;
        int fv[8];           // 存储固定物品的重量
        int fc = 0;          // 固定物品计数器
        long long s = 0;     // 固定部分的总贡献

        // 处理k个限制条件
        for (int i = 0; i < k; i++) {
            int x, y;
            cin >> x >> y;
            x--;             // 转换为0-indexed
            // 累加固定位置的贡献:a[x] * y
            s += (long long)a[x] * y;
            fv[fc++] = a[x]; // 存储物品重量
        }

        // 固定部分已超过r,无解
        if (s > r) {
            cout << 0 << '\n';
            continue;
        }

        // 计算自由部分需满足的区间[Lp, Rp]
        long long Lp = l - s;
        long long Rp = r - s;
        // 处理Lp下界(非负)
        if (Lp < 0) Lp = 0;
        // 区间无效的情况
        if (Rp < 0 || Lp > Rp) {
            cout << 0 << '\n';
            continue;
        }

        int h[N + 1];                   // 临时背包数组
        memcpy(h, g, sizeof(int) * (N + 1)); // 复制全局背包到临时数组
        sort(fv, fv + fc, greater<int>()); // 按重量降序排序(优化内存访问)

        // 退背包:移除固定物品
        for (int i = 0; i < fc; i++) {
            int av = fv[i]; // 当前物品重量
            // 逆序更新背包(完全背包逆操作)
            for (int j = N; j >= av; j--) {
                h[j] -= h[j - av];       // 移除当前物品的贡献
                if (h[j] < 0) h[j] += MOD; // 处理负数取模
            }
        }

        // 计算自由部分在[Lp, Rp]的方案数
        int ans = 0;
        // 遍历区间(注意j不超过N)
        for (int j = Lp; j <= Rp && j <= N; j++) {
            ans = (ans + h[j]) % MOD; // 累加方案数(可以加个前缀和优化,但是不加也勉强)
        }
        cout << ans << '\n';
    }
    return 0;
}

评论

1 条评论,欢迎与作者交流。

正在加载评论...