1. 程式人生 > 其它 >Acwing163 生日禮物

Acwing163 生日禮物

這題我的第一反應就是 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]\}\) 關於 \(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;
}