BZOJ3675 Apio2014 序列分割

来源:互联网 发布:网络征婚投资 编辑:程序博客网 时间:2024/05/18 18:44

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 200005using 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;}
0 0