Acwing163 生日礼物(WQS 二分)
原题链接:https://www.acwing.com/problem/content/description/165/
这题我的第一反应就是 WQS 二分,发现题解没有类似的想法,于是记录一下。
首先,考虑确定分为 \(m\) 段怎么做,我们可以设 \(f[n][m][0/1]\) 表示前 \(n\) 个数,分为 \(m\) 段,当前这个数取不取,最大的子段和。
那么显然有转移式:
\[f[n][m][0] = \max \{f[n-1][m][1], f[n-1][m][0]\}\\
f[n][m][1] = \max \begin{cases}
f[n-1][m][1] + a[n]\\
f[n-1][m-1][0] + a[n]\\
f[n-1][m-1][1] + a[n]
\end{cases}
\]
容易分析该 \(dp\) 具有上凸性,即 \(\max \{f[n][m][0], f[n][m][1]\}\) 关于 \(m\) 是个上凸函数。于是如果给定一个 \(m\),我们能够通过 WQS 二分(枚举斜率去切这个凸包)来求出答案。
可是,本题要求的是不大于 \(m\) 的最优情况,同样利用 \(dp\) 的上凸性,我们可以三分搜索这个 \(m\),再套上 WQS 二分,即可求得答案。
时间复杂度 \(O(n\log m \log \sum |A_i|)\)
这个时间复杂度还是比较高的,在 Acwing 上提交需要套上八聚氧才能通过(放在博客上就不加八聚氧了,太丑了)
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 100005;
typedef long long ll;
int n, m;
int a[maxn];
ll f[maxn][2], g[maxn][2];
pair<ll, ll> dp(ll k) {
f[0][1] = -2e9;
for (int i = 1; i <= n; i++) {
int mx = max(f[i - 1][0], f[i - 1][1]); f[i][0] = mx;
if (f[i - 1][0] == mx) g[i][0] = g[i - 1][0];
else g[i][0] = g[i - 1][1];
mx = max(f[i - 1][1] + a[i], max(f[i - 1][0] + a[i] - k, f[i - 1][1] + a[i] - k)); f[i][1] = mx;
if (f[i - 1][0] + a[i] - k == mx) g[i][1] = g[i - 1][0] + 1;
else if (f[i - 1][1] + a[i] - k == mx) g[i][1] = g[i - 1][1] + 1;
else g[i][1] = g[i - 1][1];
}
if (f[n][1] >= f[n][0]) return make_pair(f[n][1], g[n][1]);
else return make_pair(f[n][0], g[n][0]);
}
ll solve(ll x) {
ll lb = -1e9, rb = 1e9;
while (lb < rb) {
int mid = (lb + rb) >> 1; pair<ll, ll> tmp = dp(mid);
if (tmp.second > x) lb = mid + 1;
else if (tmp.second == x) { lb = mid; break; }
else rb = mid;
}
return dp(lb).first + x * lb;
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
int lb = 0, rb = m;
while (rb - lb > 2) {
int lmid = lb + (rb - lb) / 3, rmid = rb - (rb - lb) / 3;
if (solve(lmid) > solve(rmid)) rb = rmid;
else lb = lmid;
}
ll ans = 0;
for (int i = lb; i <= rb; i++) ans = max(ans, solve(i));
printf("%lld\n", ans);
return 0;
}