题解:P9506 [BalkanOI 2018] Homecoming
本人把上面所有题解都食用了一遍,还是有地方没看懂,在写代码的时候也遇到一些细节问题,所以写了这篇题解总结。如果你看前面题解有没搞懂的地方,希望这篇题解对你有帮助。
题目大意
有
环形 DP 通解
因为课本呈环形排布,所以这题是一道环形 DP。对于环形 DP,有以下两种常见解法:
- 如这道题,我们通过预设某些前提使得环状的特性不被触发(如那题中的强制不从第一天晚上睡到第二天早上)跑一遍 DP,再将这些前提反过来使得环状的特性被触发,再跑一遍 DP。适用于能够找到区分用到环的性质和不用到环的性质的题。
- 如这道题,我们通过暴力枚举答案数组的最前几项和最后几项,判断环状条件被满足后再统计答案。适用于每个数据的范围都很小的题。
考虑这题数据范围很大,所以果断使用第一种解法。
分析状态转移
我们发现,本题的环形特性就是如果靠前的教材被购买了,那么靠后的课程就有部分教材不用重复购买(因为环的存在,靠后的课程有可能需要购买靠前的教材)。那我们就可以从这个点下手:我们先假设靠后的课程转到前面去的那部分教材也需要购买。对于样例就是第
读者可能会问:那前面已经购买了第
想到这里,我们不妨考虑从哪一个
而
其中
考虑环的影响
别忘了,这是个环形 DP。我们只是考虑了转到前面去的教材也要购买的情况,还有不需要购买的情况没考虑呢!
首先我们发现,转到前面去的教材(不妨称作大于
- 强制学习第
1 门课程,此时大于n 的教材不用买,因为学了第1 门课程,第1 到第k 本教材肯定已经买了。此时的边界:dp_1=a_i-\sum_{j=1}^{k}b_i 同时如果上面状态转移方程中
b 的下标大于n 则不加进答案。 - 强制不学习第
1 门课程,此时大于n 的教材需要购买,只要按上面的方程转移即可。此时的边界:dp_1=-\infty 因为不能选嘛,所以不能从
dp_1 转移,赋成负无穷就行了。
最后的答案就是每次 DP 时所有状态的最大值。说了这么多,终于可以写代码了。
优化与细节
- 此时的代码是
O(n^2) 的。为了优化时间复杂度,我们对b 数组做前缀和,并且在状态转移过程中对dp 数组做前缀最大值。前缀和很简单,前缀最大值有一些细节。令mx_i=\max_{j=1}^{i}dp_j 那么第一种情况(强制学习第
1 门课程)时边界为mx_0=-\infty,mx_1=dp_1 因为必须上第一门课,所以不能从
dp_0 转移,设为负无穷。第二种情况(强制不学习第1 门课程)边界为mx_0=mx_1=0 注意
mx_1=0 而不是mx_1=dp_1=-\infty ,因为虽然不选第1 门课,但是从第一门课转移是合法且收益为0 的。 - 交互题传的指针参数经常越界 RE,所以在使用之前先把内容取出放到数组里。注意要给
b 数组破环为链,数组大小要开两倍。 - 开
long long。交互题不能使用#define int long long,不然solve函数传参也都变成long long了,由于 C++ 的函数重载机制,交互库识别不到函数就会 CE。
代码实现
#include <bits/stdc++.h>
using namespace std;
const int N = 4e6+5;
const long long inf = 0x3f3f3f3f3f3f3f3f;
long long a[N], b[N], dp[N], mx[N], sum[N];
long long solve(int n, int k, int *A, int *B)
{
for (int i = n; i > 0; i--) {
a[i] = A[i - 1];
b[i] = B[i - 1];
}
for (int i = 1; i < n; i++) { // 破环为链
b[i + n] = b[i];
}
for (int i = 1; i < 2 * n; i++) {
sum[i] = sum[i - 1] + b[i];
}
mx[0] = -inf;
mx[1] = dp[1] = a[1] - sum[k]; // 强制选第一个课
for (int i = 2; i <= n; i++) {
if (i + k - 1 <= n) {
dp[i] = max(dp[i - 1] + a[i] - b[i + k - 1], mx[i - 2] + a[i] - sum[i + k - 1] + sum[i - 1]);
} else {
dp[i] = max(dp[i - 1] + a[i], mx[i - 2] + a[i] - sum[n] + sum[i - 1]); // n以后的选第一个课的时候就买过了
}
mx[i] = max(mx[i - 1], dp[i]);
}
long long ans = max(0ll, mx[n]);
mx[0] = mx[1] = 0;
dp[1] = -inf; // 强制不选第一个课
for (int i = 2; i <= n; i++) {
dp[i] = max(dp[i - 1] + a[i] - b[i + k - 1], mx[i - 2] + a[i] - sum[i + k - 1] + sum[i - 1]);
mx[i] = max(mx[i - 1], dp[i]);
}
ans = max(ans, mx[n]);
return ans;
}
AC 记录,完结撒花!如果你觉得这篇题解很详细,或者对你有帮助,那就点个免费的赞吧!
管理员大大求过!