1. 程式人生 > >[APIO2014]序列分割

[APIO2014]序列分割

你正在玩一個關於長度為 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; }