【比赛】东方博宜oj 25年10月-C组(大咖)解析D
我们有:
- CCC 张购书卡,每张面值已知
- NNN 本书,按顺序排列,价格已知
- 必须按顺序买书
- 可以随时用一张卡支付从上次支付后到当前的所有书的总价
- 卡的面值必须 ≥ 这批书的总价
- 如果面值 > 总价,不找零,余额作废
- 目标:买完所有书后,剩余未使用的卡的面值总和最大
- 如果无法买完所有书,输出 -1
重点: - 书的顺序固定,所以支付批次必须是连续的区间
- 每张卡只能用一次
- 卡的使用顺序可以任意安排
- 问题本质:把书序列划分成 ≤ C 个连续区间,每个区间用一张卡支付,卡的面值 ≥ 区间和,最大化未使用的卡的面值和
因为 C≤16C \leq 16C≤16,我们可以用状态压缩 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+1到N,设下一批书是[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,说明买完了所有书 - 剩余面值 = 所有未使用的卡的面值和
- 我们要最大化这个剩余面值
=>得出算法步骤:
- 读入 C, N
- 读入 C 张卡的面值,存入数组
card - 读入 N 本书价格,计算前缀和
pref - 初始化
dp[mask] = -1(表示不可达),dp[0] = 0 - 对每个
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)
- 在
- 如果
dp[(1<<C)-1] == N:- 计算所有可能的
mask的剩余面值(未使用的卡的面值和),取最大值
- 计算所有可能的
- 否则输出 -1
复杂度
- 状态数 2C2^C2C,C ≤ 16 → 最多 65536
- 每个状态尝试 C 张卡,每张卡找最大 r 可以用二分 O(logN)O(log N)O(logN)
- 总复杂度 O(2C⋅C⋅logN)O(2^C \cdot C \cdot \log N)O(2C⋅C⋅logN),可行
验证:
输入:
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
解析D&spm=1001.2101.3001.5002&articleId=152509960&d=1&t=3&u=22bf44ec8f0b4a799d69d075eb0783ae)
11万+

被折叠的 条评论
为什么被折叠?



