Link.
给定序列 \(\{a_n\}\) 和 \(m\),定义一段区间 \([l,r]\) 的代价为 \((1+\sum_{i=l}^ra_i)^2\),求把序列划分为恰 \(m\) 段的最小代价和。
忘情(水)二分 owo!
设 WQS 二分当前一段区间的附加代价 \(k\),考虑 DP,令 \(f(i)\) 为任意划分 \([1,i]\) 的最小代价。记 \(s_i\) 为前缀和,显然:
注意到斜优的影子,令 \(g(i)=f(i)+s_i^2-2s_i\),考虑 \(i\) 的两个转移点 \(v<u\),若 \(u\) 更优,则有:
于是在 WQS 里面套一个斜优即可。复杂度 \(\mathcal O(n\log\sum_{i=1}^na_i)\)。
#include <cstdio>
typedef long long LL;
const int MAXN = 1e5;
const LL INF = 1.1e16;
int n, m, trc[MAXN + 5], que[MAXN + 5];
LL f[MAXN + 5], s[MAXN + 5];
inline int rint () {
int x = 0; char s = getchar ();
for ( ; s < '0' || '9' < s; s = getchar () );
for ( ; '0' <= s && s <= '9'; s = getchar () ) x = x * 10 + ( s ^ '0' );
return x;
}
inline LL g ( const int i ) { return f[i] + s[i] * s[i] - 2 * s[i]; }
inline double slope ( const int i, const int j ) {
return 1.0 * ( g ( i ) - g ( j ) ) / ( s[i] - s[j] );
}
inline bool check ( const LL mid, LL& res ) {
for ( int head, tail, i = head = tail = 1; i <= n; ++ i ) {
for ( ; head < tail && slope ( que[head], que[head + 1] ) < 2 * s[i]; ++ head );
trc[i] = trc[que[head]] + 1;
f[i] = f[que[head]] + ( s[i] - s[que[head]] + 1 ) * ( s[i] - s[que[head]] + 1 ) + mid;
for ( ; head < tail && slope ( que[tail - 1], que[tail] ) > slope ( que[tail], i ); -- tail );
que[++ tail] = i;
}
return trc[n] <= m ? res = f[n] - m * mid, false : true;
}
int main () {
n = rint (), m = rint ();
for ( int i = 1; i <= n; ++ i ) s[i] = s[i - 1] + rint ();
LL l = -INF, r = INF, ans = 0;
while ( l <= r ) {
LL mid = l + r >> 1;
if ( check ( mid, ans ) ) l = mid + 1;
else r = mid - 1;
}
printf ( "%lld\n", ans );
return 0;
}