1. 程式人生 > 實用技巧 >Solution -「洛谷 P4983」忘情

Solution -「洛谷 P4983」忘情

\(\mathcal{Description}\)

  Link.

  給定序列 \(\{a_n\}\)\(m\),定義一段區間 \([l,r]\) 的代價為 \((1+\sum_{i=l}^ra_i)^2\),求把序列劃分為恰 \(m\) 段的最小代價和。

\(\mathcal{Solution}\)

  忘情(水)二分 owo!

  設 WQS 二分當前一段區間的附加代價 \(k\),考慮 DP,令 \(f(i)\) 為任意劃分 \([1,i]\) 的最小代價。記 \(s_i\) 為字首和,顯然:

\[\begin{aligned} f(i)&=\min_{j\in[0,i)}\{f(j)+(s_i-s_j+1)^2\}+k\\ &=k+(s_i+1)^2+\min_{j\in[0,i)}\{(f(j)+s_j^2-2s_j)-2s_is_j\} \end{aligned} \]

  注意到斜優的影子,令 \(g(i)=f(i)+s_i^2-2s_i\),考慮 \(i\) 的兩個轉移點 \(v<u\),若 \(u\) 更優,則有:

\[g(u)-2s_is_u<g(v)-2s_is_v\\ \Rightarrow \frac{g(u)-g(v)}{s_u-s_v}<2s_i \]

  於是在 WQS 裡面套一個斜優即可。複雜度 \(\mathcal O(n\log\sum_{i=1}^na_i)\)

\(\mathcal{Code}\)

#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;
}