LG3648 [APIO2014] 序列分割
Problem
Analysis
正解思路
序列分割很容易想到两种思考:
- 反向转化为如何合并,区间 dp 解决。
- 证明分割顺序无影响,采用线性 dp 解决。
但显然这个方法一无法进行优化。那么考虑方法二,然后就比较容易证明出分割顺序确实无影响,因为对于任意一个块内的每个元素都与其他块内的所有元素相乘过。
然后就考虑 dp 状态如何设计:设 \(dp_{i,j}\) 为前 \(i\) 个元素划分了 \(j\) 次的最大价值。转移也比较显然:枚举前一个划分点 \(k\),有 \(dp_{i,j}\gets \max_{1\le k<i}\{dp_{k,j-1}+(sum_n-sum_i)(sum_i-sum_j)\}\)。此时复杂度为 \(O(n^2k)\)。
仍然无法通过,状态已经很优了,所以考虑优化转移。
发现这个转移方程符合斜率优化的条件,那么就能优化到 \(O(nk)\)。
设 \(k_1<k_2\),若 \(dp_{k_1}+(sum_n-sum_i)(sum_i-sum_{k_1})>dp_{k_2}+(sum_n-sum_i)(sum_i-sum_{k_2})\),则选择 \(k_1\),否则 \(k_2\)。继续转化不等式可得 \(\frac{dp_{k_2}-dp_{k_1}}{sum_{k_2}-sum_{k_1}}<sum_n-sum_i\)。(dp 第二维省略)
此时将 \((sum_k,dp_k)\) 看作平面上的一个点,那么就可以用单调队列维护。
错因总结
考虑了分割顺序是无影响的,但是没有想到其证明方法,且脑子中想的转移方程不清楚,如果写在纸上则可能可以写出朴素的转移方程,但是最后一步斜率优化可能还是想不到(已经遗忘了)。
AC Code
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define i128 __int128
#define fi first
#define se second
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define mk make_pair
#define INF 0x3f3f3f3f
#define INFx 0x3f3f3f3f3f3f3f3f
using namespace std;
const int N = 1e5 + 10, M = 210;
int n, k;
ll a[N], s[N];
ll dp[N][M];
int from[N][M];
int q[N][M], hh[M], tt[M]; // 第 i 层的单调队列
int seq[M];
double slope(int j, int k1, int k2) {
if (s[k1] == s[k2]) return 1e18;
return (double)(dp[k2][j] - dp[k1][j]) / (s[k2] - s[k1]);
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i ++) {
cin >> a[i];
s[i] = s[i - 1] + a[i];
}
for (int i = 1; i <= n; i ++) {
double lim = s[n] - s[i];
for (int j = k; j >= 1; j --) {
int &h1 = hh[j - 1], &t1 = tt[j - 1];
while (h1 <= t1 - 1 && slope(j - 1, q[h1][j - 1], q[h1 + 1][j - 1]) >= lim) h1 ++;
int t = q[h1][j - 1];
dp[i][j] = dp[t][j - 1] + (ll)(s[n] - s[i]) * (s[i] - s[t]);
from[i][j] = t;
int &h2 = hh[j], &t2 = tt[j];
while (h2 <= t2 - 1 && slope(j, q[t2][j], i) >= slope(j, q[t2 - 1][j], q[t2][j])) t2 --;
q[++ t2][j] = i;
}
}
ll ans = -1;
int p = 0;
for (int i = 1; i <= n; i ++) { // final cut
if (dp[i][k] > ans) ans = dp[i][k], p = i;
}
for (int i = k; i >= 1; i --) {
seq[i] = p;
p = from[p][i];
}
cout << ans << '\n';
for (int i = 1; i <= k; i ++) {
cout << seq[i] << " ";
}
return 0;
}

浙公网安备 33010602011771号