【比赛】东方博宜oj 25年10月-C组(大咖)解析D

【比赛】东方博宜oj 25年10月-C组(大咖)解析D


我们有:

  • CCC 张购书卡,每张面值已知
  • NNN 本书,按顺序排列,价格已知
  • 必须按顺序买书
  • 可以随时用一张卡支付从上次支付后到当前的所有书的总价
  • 卡的面值必须 ≥ 这批书的总价
  • 如果面值 > 总价,不找零,余额作废
  • 目标:买完所有书后,剩余未使用的卡的面值总和最大
  • 如果无法买完所有书,输出 -1
    重点:
  • 书的顺序固定,所以支付批次必须是连续的区间
  • 每张卡只能用一次
  • 卡的使用顺序可以任意安排
  • 问题本质:把书序列划分成 ≤ C 个连续区间,每个区间用一张卡支付,卡的面值 ≥ 区间和,最大化未使用的卡的面值和
    因为 C≤16C \leq 16C16,我们可以用状态压缩 DP
    设:
  • 书的价格前缀和 pref[i] = 前 i 本书的总价
  • 区间 [l, r] 的总价 = pref[r] - pref[l-1]
    定义 dp[mask] 表示已经使用过的卡集合为 mask 时,已经支付到的书的最后一本的索引(即已买完前 dp[mask] 本书)。
    转移:
  • 初始状态:dp[0] = 0,表示还没用任何卡,买了 0 本书
  • 对每个状态 mask,已知已买到书 pos = dp[mask]
  • 尝试下一批书:从 pos+1N,设下一批书是 [pos+1, r],总价 sum = pref[r] - pref[pos]
  • 找一张还没用过的卡 j,其面值 card[j] >= sum
  • 用这张卡支付这批书,新状态 new_mask = mask | (1<<j)
  • 更新 dp[new_mask] = max(dp[new_mask], r)
    最终:
  • 如果 dp[(1<<C)-1] == N,说明买完了所有书
  • 剩余面值 = 所有未使用的卡的面值和
  • 我们要最大化这个剩余面值
    =>得出算法步骤:
  1. 读入 C, N
  2. 读入 C 张卡的面值,存入数组 card
  3. 读入 N 本书价格,计算前缀和 pref
  4. 初始化 dp[mask] = -1(表示不可达),dp[0] = 0
  5. 对每个 mask,如果 dp[mask] >= 0
    • pos = dp[mask]
    • 对每张未用的卡 j
      • [pos+1, N] 范围内二分(或直接枚举)找到最大的 r,使得 pref[r] - pref[pos] <= card[j]
      • 如果存在这样的 r,更新 dp[new_mask] = max(dp[new_mask], r)
  6. 如果 dp[(1<<C)-1] == N
    • 计算所有可能的 mask 的剩余面值(未使用的卡的面值和),取最大值
  7. 否则输出 -1
    复杂度
  • 状态数 2C2^C2C,C ≤ 16 → 最多 65536
  • 每个状态尝试 C 张卡,每张卡找最大 r 可以用二分 O(logN)O(log N)O(logN)
  • 总复杂度 O(2C⋅C⋅log⁡N)O(2^C \cdot C \cdot \log N)O(2CClogN),可行
    验证:
    输入:
3 6
12
15
10
6
3
3
2
3
7

卡片:[12, 15, 10]
书价:[6,3,3,2,3,7],前缀和:[0,6,9,12,14,17,24]
最优:

  • 用卡2(面值10)支付 [1,2]:和=9,pos=2
  • 用卡1(面值15)支付 [3,6]:和=15,pos=6
  • 剩余卡0(面值12)
    剩余面值=12
    上代码!!!!!!!!
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;

int main() {
    int C, N;
    cin >> C >> N;
    
    vector<int> card(C);
    for (int i = 0; i < C; i++) {
        cin >> card[i];
    }
    
    vector<int> price(N + 1);
    vector<long long> pref(N + 1, 0);
    for (int i = 1; i <= N; i++) {
        cin >> price[i];
        pref[i] = pref[i - 1] + price[i];
    }
    
    vector<int> dp(1 << C, -1);
    dp[0] = 0;
    
    for (int mask = 0; mask < (1 << C); mask++) {
        if (dp[mask] == -1) continue;
        int pos = dp[mask];
        for (int j = 0; j < C; j++) {
            if (mask >> j & 1) continue; // 卡已用过
            long long card_val = card[j];
            // 二分找到能支付的最大 r
            int l = pos + 1, r = N, ans = -1;
            while (l <= r) {
                int mid = (l + r) / 2;
                if (pref[mid] - pref[pos] <= card_val) {
                    ans = mid;
                    l = mid + 1;
                } else {
                    r = mid - 1;
                }
            }
            if (ans != -1) {
                int new_mask = mask | (1 << j);
                dp[new_mask] = max(dp[new_mask], ans);
            }
        }
    }
    
    int full_mask = (1 << C) - 1;
    if (dp[full_mask] != N) {
        cout << -1 << endl;
        return 0;
    }
    
    int max_remain = 0;
    for (int mask = 0; mask < (1 << C); mask++) {
        if (dp[mask] == N) {
            int remain = 0;
            for (int j = 0; j < C; j++) {
                if (!(mask >> j & 1)) {
                    remain += card[j];
                }
            }
            max_remain = max(max_remain, remain);
        }
    }
    
    cout << max_remain << endl;
    
    return 0;
}

说明:

  • 这里 dp[mask] 表示用 mask 这些卡能买到的最大书索引
  • 最终可能多个 mask 都能买完 N 本书,我们取剩余面值最大的
  • 二分查找是为了快速确定一张卡能支付到哪本书
    这样就能在给定数据范围内高效求解。

又不知不觉写了238323832383字啦~~

好啦,支持一下作者叭
看到这里了,不点个赞再走嘛?
QWQ,开玩笑的!!!!!!不要真走啊!!!!
来我主页逛逛叭~
顺带关注一下也挺好的(小害羞),不过看来读者大大们是不大可能的o(╥﹏╥)o

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

my小天神

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值