BZOJ3675 Apio2014 序列分割


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 3

108


Solution
二維的斜率優化題。

這題的難點在於如何理解最後得到的分數與切的順序無關。可以這麼想:從某段序列中間切一刀,兩邊和相乘,看做左邊的每小段區間和右邊的每小段區間分別相乘。異側的區間處理完了,接下來就要處理同側的區間,此時就採用分治的思想,將問題劃歸到左區間內和右區間內,這樣最後無論如何一定都是得到每個小區間兩兩相乘的結果。同樣,這個中間軸也可以任意變換,最後得到的答案顯然是一樣的——因爲所有不同的遞歸方式都是在解決同一個問題:得到每個小區間兩兩相乘的乘積,複雜度還是O(nlogn) 的。

還有個問題,之前討論的題目都是不確定劃分出多少個區間的解法,現在唯一確定了要劃分出幾個區間。比較顯然的思路就是枚舉劃分出了幾個區間。這樣將dp數組滾動一下即可。 方程:

dp[k][i]=minj=0i1{dp[k1][j]+t=j+1iA[t]p=1jA[p]}
g(x,y,k)=dp[k1][x]dp[k1][y]+sum[y]2sum[x]2sum[y]sum[x]
#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章