题目链接
游记链接
我开场第一个开的题就是这个,误判断以为是个我不会的中档贪心题,结果扔给 djq 他都想了好久,然后我就自闭了 2333
真·hard 神题,给出题人点赞!接下来大概就是把英文题解翻译了一遍吧,顺便补了一点证明。
最初始的贪心部分,相信大家都想到了。我们把怪物分成 A i > B i A_i > B_i Ai>Bi 和 A i ≤ B i A_i \le B_i Ai≤Bi 的部分,分别记为集合 S , T S,T S,T,那么有以下性质:
于是整道题就被我们分成了几部分:
其中第一部分最难。
考虑一个 trival 的 dp,把 S S S 按照 B B B 从大到小排序, f ( i , j ) f(i,j) f(i,j) 表示当前考虑了最后 i i i 个元素,选择 j j j 个所需要的最小代价。那么显然有如下 dp 方程:
f ( i , j ) = min { max { f ( i − 1 , j − 1 ) − B i , 0 } + A i , f ( i − 1 , j ) } f(i,j)=\min\{\max\{f(i-1,j-1)-B_i,0\}+A_i,f(i-1,j)\} f(i,j)=min{max{f(i−1,j−1)−Bi,0}+Ai,f(i−1,j)}
仔细分析一下这个 dp 方程,我们发现对于一个 i i i,它有四种不同的转移:
引理: 令 j 0 = min { j ∣ f ( i , j ) > B i } j_0=\min\{j|f(i,j) > B_i\} j0=min{j∣f(i,j)>Bi},那么对于任意的 j 0 < j < i j_0 < j < i j0<j<i,都有 f ( i , j + 1 ) − f ( i , j ) ≥ f ( i , j ) − f ( i , j − 1 ) f(i,j+1)-f(i,j) \ge f(i,j)-f(i,j-1) f(i,j+1)−f(i,j)≥f(i,j)−f(i,j−1),并且 f ( i , j 0 + 1 ) − f ( i , j 0 ) ≥ f ( i , j 0 ) − B i f(i,j_0+1)-f(i,j_0) \ge f(i,j_0)-B_i f(i,j0+1)−f(i,j0)≥f(i,j0)−Bi。即删掉所有 f ( i , j ) ≤ B i f(i,j) \le B_i f(i,j)≤Bi,并且在开头补一个 B i B_i Bi,则剩下的东西是个下凸函数。
证明前的准备: 两条显然的不等式, f ( i , j ) ≤ f ( i − 1 , j ) f(i,j) \le f(i-1,j) f(i,j)≤f(i−1,j), f ( i , j ) ≥ f ( i , j − 1 ) f(i,j) \ge f(i,j-1) f(i,j)≥f(i,j−1)。
接下来的证明都是博主 yy 的,如果有伪证请指出
证明: 考虑数学归纳, i = 1 i=1 i=1 时显然满足。我们首先证明 f ( i , j 0 + 1 ) − f ( i , j 0 ) ≥ f ( i , j 0 ) − B i f(i,j_0+1)-f(i,j_0) \ge f(i,j_0)-B_i f(i,j0+1)−f(i,j0)≥f(i,j0)−Bi,首先我们有 f ( i , j 0 − 1 ) = f ( i − 1 , j 0 − 1 ) ≤ B i f(i,j_0-1)=f(i-1,j_0-1) \le B_i f(i,j0−1)=f(i−1,j0−1)≤Bi,这是因为 f ( i , j 0 − 1 ) f(i,j_0-1) f(i,j0−1) 不可能从情况 4 转移过来,否则就会 ≥ A i \ge A_i ≥Ai。我们分类讨论一下:
其实接下来对于任意的 j 0 < j < i j_0 < j < i j0<j<i,都有 f ( i , j + 1 ) − f ( i , j ) ≥ f ( i , j ) − f ( i , j − 1 ) f(i,j+1)-f(i,j) \ge f(i,j)-f(i,j-1) f(i,j+1)−f(i,j)≥f(i,j)−f(i,j−1) 反而好证一些了。我们考虑一个分界点 t t t 使得对于 j ≥ t j \ge t j≥t 都是从情况 4 转移来的,否则都是从 1,2,3 转移来的。首先对于 j < t j
对于分界点 t t t,有 f ( i , t ) − f ( i , t − 1 ) = A i − B i f(i,t)-f(i,t-1)=A_i-B_i f(i,t)−f(i,t−1)=Ai−Bi,根据转移条件,我们有 f ( i − 1 , t − 1 ) − f ( i − 1 , t − 2 ) < A i − B i f(i-1,t-1)-f(i-1,t-2)
对于 j > t j>t j>t,我们有 f ( i , j + 1 ) − f ( i , j ) = f ( i − 1 , j ) − f ( i − 1 , j − 1 ) ≥ f ( i − 1 , j − 1 ) − f ( i − 1 , j − 2 ) = f ( i , j ) − f ( i , j − 1 ) f(i,j+1)-f(i,j)=f(i-1,j)-f(i-1,j-1) \ge f(i-1,j-1)-f(i-1,j-2)=f(i,j)-f(i,j-1) f(i,j+1)−f(i,j)=f(i−1,j)−f(i−1,j−1)≥f(i−1,j−1)−f(i−1,j−2)=f(i,j)−f(i,j−1)。
证毕。
因此扔掉 ≤ B i \le B_i ≤Bi 的部分,并在最前面补个 B i B_i Bi,确实是个上凸函数。然后就是比较 naive 的部分了,直接上个优先队列维护差分数组,每次扔掉 ≤ B i \le B_i ≤Bi 的部分,并且更新一下第一个值(因为第一个点从 B i − 1 B_{i-1} Bi−1 变成 B i B_i Bi 了)。然后再往优先队列里扔一个 A i − B i A_i-B_i Ai−Bi(决策分界点的差分)即可。复杂度 O ( n log n ) O(n \log n) O(nlogn)。
这个部分我都在想要不要额外开一个小标题,由于强迫症我还是开了。
由于 A i ≤ B i A_i \le B_i Ai≤Bi,根据观察,直接选择 K K K 个最小的 A i A_i Ai 即可。
我们知道了选择 i i i 个集合 T T T 中元素的最小代价 c i c_i ci,即初始能量在 [ c i , c i + 1 ) [c_i,c_{i+1}) [ci,ci+1) 时我们都只能选 i i i 个。于是我们就知道了如果初始能量在这段区间中,能量会增加多少,设为 d i d_i di。我们找到所有 j j j 表示在 S S S 中选 j j j 个,并且 i + j i+j i+j 这个个数还没有被计算过的 j j j, f j ≤ c i + 1 + d i − 1 f_j \le c_{i+1}+d_i-1 fj≤ci+1+di−1 的,那么它需要的初始代价就是 max { c i , f j − d i } \max\{c_i,f_j-d_i\} max{ci,fj−di}。
由于 c i , d i c_i,d_i ci,di 显然都单增,因此可以做到线性时间。
综上,我们就得到了一个复杂度 O ( n log n ) O(n \log n) O(nlogn) 的做法。
#include
typedef long long LL;
using namespace std;
template<typename T> void chkmax(T &a, const T &b) { a = a < b ? b : a; }
template<typename T> void chkmin(T &a, const T &b) { a = a < b ? a : b; }
const int MAXN = 300005, MOD = 1e9 + 7;
struct Data { int a, b; } S[MAXN], T[MAXN];
priority_queue<int, vector<int>, greater<int> > pq;
int aa[MAXN], bb[MAXN], n, C;
LL f[MAXN], c[MAXN], d[MAXN];
bool cmpa(const Data &a, const Data &b) { return a.a < b.a; }
bool cmpb(const Data &a, const Data &b) { return a.b < b.b; }
int main() {
for (scanf("%d", &C); C--;) {
scanf("%d", &n);
int ns = 0, nt = 0;
for (int i = 1; i <= n; i++) scanf("%d", aa + i);
for (int i = 1; i <= n; i++) scanf("%d", bb + i);
for (int i = 1; i <= n; i++) {
int a = aa[i], b = bb[i];
if (a <= b) T[++nt] = Data { a, b };
else S[++ns] = Data { a, b };
}
sort(S + 1, S + 1 + ns, cmpb);
sort(T + 1, T + 1 + nt, cmpa);
int cnt = 0;
LL low = 0;
for (int i = 1; i <= ns; i++) {
while (!pq.empty()) {
int sum = pq.top() + low;
if (sum > S[i].b) break;
low = sum;
f[++cnt] = low;
pq.pop();
}
if (!pq.empty()) {
int sum = pq.top() + low - S[i].b;
pq.pop();
pq.push(sum);
}
pq.push(S[i].a - S[i].b);
low = S[i].b;
}
for (; !pq.empty(); pq.pop())
f[++cnt] = (low += pq.top());
for (int i = 1; i <= nt; i++) {
c[i] = max(T[i].a - c[i - 1] - d[i - 1], 0ll) + c[i - 1];
d[i] = d[i - 1] + T[i].b - T[i].a;
}
c[nt + 1] = 1e18;
LL ans = 0; cnt = 0;
for (int i = 0, j = 0; i <= nt; i++) {
if (c[i] == c[i + 1]) continue;
LL r = c[i + 1] - 1 + d[i];
while (j < ns && f[j + 1] <= r) ++j;
for (int k = cnt + 1; k <= i + j; k++) {
LL t = max(f[k - i] - d[i], c[i]);
ans = (ans + t % MOD * k) % MOD;
// printf("%lld ", t);
}
cnt = i + j;
}
printf("%lld\n", ans);
}
return 0;
}