Description
小H最近迷上了一個分隔序列的遊戲。在這個遊戲裏,小H需要將一個長度爲n的非負整數序列分割成k+1個非空的子序列。爲了得到k+1個子序列,小H需要重複k次以下的步驟:
1.小H首先選擇一個長度超過1的序列(一開始小H只有一個長度爲n的序列,也就是一開始得到的整個序列);
2.選擇一個位置,並通過這個位置將這個序列分割成連續的兩個非空的新序列。每次進行上述步驟之後,小H將會得到一定的分數。這個分數爲兩個新序列中元素和的乘積。小H希望選擇一種最佳的分割方式,使得k輪之後,小H的總得分最大。
Sample Input &Output
7 3
4 1 3 4 0 2 3108
二維的斜率優化題。
這題的難點在於如何理解最後得到的分數與切的順序無關。可以這麼想:從某段序列中間切一刀,兩邊和相乘,看做左邊的每小段區間和右邊的每小段區間分別相乘。異側的區間處理完了,接下來就要處理同側的區間,此時就採用分治的思想,將問題劃歸到左區間內和右區間內,這樣最後無論如何一定都是得到每個小區間兩兩相乘的結果。同樣,這個中間軸也可以任意變換,最後得到的答案顯然是一樣的——因爲所有不同的遞歸方式都是在解決同一個問題:得到每個小區間兩兩相乘的乘積,複雜度還是
還有個問題,之前討論的題目都是不確定劃分出多少個區間的解法,現在唯一確定了要劃分出幾個區間。比較顯然的思路就是枚舉劃分出了幾個區間。這樣將dp數組滾動一下即可。 方程:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define M 200005
using namespace std;
long long sum[M],dp[2][M];
int deq[M],n,K;
long long sqr(long long x){return x*x;}
long long calc(int x,int y,int k){return dp[k][x]+(sum[y]-sum[x])*sum[x];}
long long up(int x,int y,int k){return dp[k][x]-dp[k][y]+sqr(sum[y])-sqr(sum[x]);}
long long down(int x,int y){return sum[y]-sum[x];}
int main(){
scanf("%d %d",&n,&K);
for(int i=1,x;i<=n;i++){
scanf("%d",&x);
sum[i]=sum[i-1]+x;
}
int cur=1;
for(int k=1;k<=K;k++){
cur^=1;
int L=0,R=-1;
deq[++R]=k-1;
for(int i=k;i<=n;i++){
while(L<R&&up(deq[L],deq[L+1],cur^1)<sum[i]*down(deq[L],deq[L+1]))++L;
dp[cur][i]=calc(deq[L],i,cur^1);
while(L<R&&up(deq[R-1],deq[R],cur^1)*down(deq[R],i)>=up(deq[R],i,cur^1)*down(deq[R-1],deq[R]))--R;
deq[++R]=i;
}
}
cout<<dp[cur][n]<<endl;
return 0;
}