[APIO2014]序列分割——[斜率优化DP]

在这里插入图片描述
【题意分析】

题意就是把一个序列切k刀,每次的得分为序列两边元素之和的乘积

下证答案与切割的顺序无关,只与切割的位置有关。
在这里插入图片描述
//这是一个丑陋的图

假设你切在红色的两个位置,那么就有先切ab处再切bc处或者先切bc处再切ab处两种切法。

a n s 1 = a ( b + c ) + b c = a b + a c + b c ans1=a(b+c)+bc=ab+ac+bc ans1=a(b+c)+bc=ab+ac+bc

a n s 2 = ( a + b ) c + a b = a b + a c + b c ans2=(a+b)c+ab=ab+ac+bc ans2=(a+b)c+ab=ab+ac+bc

得证。

然后我们设状态dp[i][j]为前i个切j刀的最大收益,那就枚举切的位置k

d p i , j = max ⁡ 1 ≤ k < i { d p k , j − 1 + s u m k ∗ ( s u m i − s u m k ) } dp_{i,j}=\max_{1\leq k<i}\{dp_{k,j-1}+sum_k*(sum_i-sum_k)\} dpi,j=1k<imax{dpk,j1+sumk(sumisumk)}

设两个决策x,y,x大于y且决策x比决策y更优

d p x + s u m x ∗ ( s u m i − s u m x ) > d p y + s u m y ∗ ( s u m i − s u m y ) dp_x+sum_x*(sum_i-sum_x)>dp_y+sum_y*(sum_i-sum_y) dpx+sumx(sumisumx)>dpy+sumy(sumisumy)

⇒ d p x − d p y + s u m y 2 − s u m x 2 s u m y − s u m x < s u m i \Rightarrow \frac{dp_x-dp_y+sum_y^2-sum_x^2}{sum_y-sum_x}<sum_i sumysumxdpxdpy+sumy2sumx2<sumi

然后直接斜率优化

注意0<=ai<=10^4,这意味着 s u m i sum_i sumi不一定递增, s u m x sum_x sumx可能等于 s u m y sum_y sumy,那么你就NaN了

所以slope的时候判一下相等return -1e18就好了

至于单调队列的等号条件,加与不加其实没有关系

内存512M可以不滚动(128M–的话就要滚动了),但滚动更好

Code:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <algorithm>
#define int long long
#define db long double
#define MAXN 100002
using namespace std;

int dp[MAXN][3], q[MAXN], path[MAXN][202], sum[MAXN], n, k;

inline int read () {
	register int s = 0, w = 1;
	register char ch = getchar ();
	while (! isdigit (ch)) ch = getchar ();
	while (isdigit (ch)) {s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar ();}
	return s * w;
}

inline db slope (int x, int y, int o) {
	if (sum[x] == sum[y]) return -1e18;
	return 1.0 * (dp[x][o] - dp[y][o] + sum[y] * sum[y] - sum[x] * sum[x]) / (sum[y] - sum[x]);
}

signed main () {
	n = read (), k = read ();
	for (register int i = 1; i <= n; i++) sum[i] = sum[i - 1] + read ();
	for (register int j = 1; j <= k; j++) {
		int h = 0, t = 0; memset (q, 0, sizeof q);
		int now = j & 1, pre = now ^ 1;
		for (register int i = 1; i <= n; i++) {
			while (h < t && slope (q[h + 1], q[h], pre) <= sum[i]) h++;
			dp[i][now] = dp[q[h]][pre] + sum[q[h]] * (sum[i] - sum[q[h]]), path[i][j] = q[h];
			while (h < t && slope (q[t], q[t - 1], pre) >= slope (i, q[t], pre)) t--;
			q[++t] = i;
		}
	}
	printf ("%lld\n", dp[n][k & 1]); int now = n;
	for (register int i = k; i >= 1; now = path[now][i--]) printf ("%lld ", path[now][i]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值