[APIO2014]序列分割
阿新 • • 發佈:2019-02-05
你正在玩一個關於長度為 nn 的非負整數序列的遊戲。這個遊戲中你需要把序列分成 k+1k+1 個非空的塊。為了得到 k+1k+1 塊,你需要重複下面的操作 kk 次:
選擇一個有超過一個元素的塊(初始時你只有一塊,即整個序列)
選擇兩個相鄰元素把這個塊從中間分開,得到兩個非空的塊。
每次操作後你將獲得那兩個新產生的塊的元素和的乘積的分數。你想要最大化最後的總得分。
輸入格式
第一行包含兩個整數 nn 和 kk。保證 k+1≤nk+1≤n。
第二行包含 nn 個非負整數 a1,a2,…,ana1,a2,…,an (0≤ai≤104)(0≤ai≤104),表示前文所述的序列。
輸出格式
第一行輸出你能獲得的最大總得分。
第二行輸出 kk 個介於 11 到 n−1n−1 之間的整數,表示為了使得總得分最大,你每次操作中分開兩個塊的位置。第 ii 個整數 sisi 表示第 ii 次操作將在 sisi 和 si+1si+1 之間把塊分開。
如果有多種方案使得總得分最大,輸出任意一種方案即可。
樣例一
input
7 3
4 1 3 4 0 2 3
output
108
1 3 5
explanation
你可以通過下面這些操作獲得 108108 分:
初始時你有一塊 (4,1,3,4,0,2,3)(4,1,3,4,0,2,3)。在第 11 個元素後面分開,獲得 4×(1+3+4+0+2+3)=524×(1+3+4+0+2+3)=52 分。 你現在有兩塊 (4),(1,3,4,0,2,3)(4),(1,3,4,0,2,3)。在第 33 個元素後面分開,獲得 (1+3)×(4+0+2+3)=36(1+3)×(4+0+2+3)=36 分。 你現在有三塊 (4),(1,3),(4,0,2,3)(4),(1,3),(4,0,2,3)。在第 55 個元素後面分開,獲得 (4+0)×(2+3)=20(4+0)×(2+3)=20 分。
所以,經過這些操作後你可以獲得四塊 (4),(1,3),(4,0),(2,3)(4),(1,3),(4,0),(2,3) 並獲得 52+36+20=10852+36+20=108 分。
限制與約定
第一個子任務共 11 分,滿足 1≤k
/*
50分的暴力: 複雜度: O(kn^2)
首先要發現一個性質: 只要選定了切的位置,不論什麼順序切,答案都是一樣
例如: 把序列切兩次,每段的和分為 x1 x2 x3,
先在1,2間切的得分: x1*(x2+x3)+x2*x3 = x1*x2+x1*x3+x2*x3
先在1,3間切的得分: x3*(x1+x2)+x1*x2 = x1*x3+x1*x2+x1*x2
所以就可以
用dp(i,j) 表示前j個數切i次的總得分,sum是字首和
dp(i,j) = max(dp(i-1, k) + (sum(j)-sum(k))*sum(k));
k是列舉<j的區間裡的一個切點
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
using namespace std;
const int maxx=1e5+5;
#define For(i,a,b) for(register int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(register int i=(a);i>=(b);--i)
#define LL long long
int read(){
char x=getchar(); int u=0;
while(!isdigit(x)) x=getchar();
while(isdigit(x)) u=(u<<3)+(u<<1)+(x^48), x=getchar();
return u;
}
LL sum[maxx],dp[201][maxx],to[201][maxx],n,k;
int main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
#endif
n=read(); k=read();
For(i,1,n)
sum[i]=sum[i-1]+read();
For(i,1,n)
For(j,1,i-1)
For(p,1,k)
if(dp[p][i]<=dp[p-1][j]+sum[j]*(sum[i]-sum[j])){
dp[p][i]=dp[p-1][j]+sum[j]*(sum[i]-sum[j]);
to[p][i]=j;
}
printf("%lld\n",dp[k][n]);
int i=n;
Rep(j,k,1){
i=to[j][i];
printf("%d ",i);
}
return 0;
}
/*
在50分的暴力基礎上,因為原式大概為
dp(i)=max(dp(k) + sum(i) * sum(k) - sum(k)^2);
可以斜率優化
假設 j<k 即可以從k轉移
sum(i) > [ sum(j)^2-sum(k)^2 + dp(k) - dp(j) ] / (sum(j) -sum(k);
輸出方案時,只需要記錄每次由誰轉移
洛谷上只開了128M,uoj上開了256M,所以洛谷上會MLE
幸好dp(i,j) =max(dp(i-1, k) +sum(...))
每次只由上一層轉移過來,所以可以開滾動陣列
*/
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
using namespace std;
const int maxx=1e5+1;
#define For(i,a,b) for(register int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(register int i=(a);i>=(b);--i)
#define LL long long
#define sqr(x) ((x)*(x))
int read(){
char x=getchar(); int u=0;
while(!isdigit(x)) x=getchar();
while(isdigit(x)) u=(u<<3)+(u<<1)+(x^48), x=getchar();
return u;
}
LL sum[maxx],dp[2][maxx],n,k,p;
int to[201][maxx];
int q[maxx],l,r;
bool type=0;
double slope(int x,int y){ // 斜率
if(sum[x]==sum[y]) return -1e18;
return (double)(sqr(sum[x]) - sqr(sum[y])-dp[type^1][x] + dp[type^1][y]) / (double) (sum[x]-sum[y]);
}
int main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
#endif
n=read(); k=read();
For(i,1,n)
sum[i]=sum[i-1]+read();
for(p=1; p<=k; ++p){
l=r=0; type^=1;
For(i,1,n){
while(l<r && slope(q[l],q[l+1]) <= sum[i]) ++l;
dp[type][i]=dp[type^1][q[l]]+sum[q[l]]*(sum[i]-sum[q[l]]);
to[p][i]=q[l];
while(l<r && slope(q[r-1],q[r]) >= slope(q[r],i)) --r;
q[++r]=i;
}
}
printf("%lld\n",dp[type][n]);
int i=n;
Rep(j,k,1){
i=to[j][i];
printf("%d ",i);
}
return 0;
}