Description
你正在玩一个关于长度为
n
的非负整数序列的游戏。这个游戏中你需要把序列分成
1.选择一个有超过一个元素的块(初始时你只有一块,即整个序列)
2.选择两个相邻元素把这个块从中间分开,得到两个非空的块。
每次操作后你将获得那两个新产生的块的元素和的乘积的分数。你想要最大化最后的总得分。
Input
第一行包含两个整数
第二行包含
n
个非负整数
Output
第一行输出你能获得的最大总得分。
第二行输出
k
个介于
如果有多种方案使得总得分最大,输出任意一种方案即可。
Sample Input
7 3
4 1 3 4 0 2 3
Sample Output
108
1 3 5
Hint
2≤n≤100000,1≤k≤min{n−1,200}
题解
bzoj上的这题是阉割版,uoj上有完整的数据和spj。
设
fp,i
为切p次,
1…i
区间最高价值。
显然
fp,i=max{fp−1,j+sj×(si−sj)}
然后斜率规划的那一套理论,如果
j<k
且从
j
转移更优,那么:
转移的时候注意分母可能为0,那样的话需要特殊判断一下,且比较式要带上等号。
输出方案的话记录每个状态是从哪儿转移而来的,因为任意一种都可以,那么顺序也是无关紧要的。
#include<bits/stdc++.h>
using namespace std;
#define rep(i, l, r) for(int i = (l); i <= (r); i++)
#define per(i, r, l) for(int i = (r); i >= (l); i--)
#define sqr(x) ((x)*(x))
typedef long long ll;
const int N = 100000 + 10;
ll f[N][2], a[N], s[N];
int fr[N][210], n, k, x, y, q[N];
inline int read(){
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)) { if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)) { x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
inline double slope(int i, int j){
if(s[i] == s[j]) return -1e20;
return (sqr(s[i]) - f[i][y] - sqr(s[j]) + f[j][y]) / (double)(s[i] - s[j]);
}
void init(){
n = read(); k = read();
rep(i, 1, n) a[i] = read(), s[i] = s[i-1] + a[i];
}
void work(){
int l, r;
rep(p, 1, k){
x = p & 1; y = x ^ 1;
q[0] = r = l = 0;
rep(i, 1, n){
while(l < r && slope(q[l], q[l+1]) <= s[i]) l++;
f[i][x] = f[q[l]][y] + (s[i] - s[q[l]]) * s[q[l]];
fr[i][p] = q[l];
while(l < r && slope(q[r], i) <= slope(q[r-1], q[r])) r--;
q[++r] = i;
}
}
printf("%lld\n", f[n][x]);
int i = n; per(j, k, 1){printf("%d ", fr[i][j]); i = fr[i][j];}
}
int main(){
init();
work();
return 0;
}