BZOJ 3675: [Apio2014]序列分割
3675: [Apio2014]序列分割
Description
小H最近迷上了一个分隔序列的游戏。在这个游戏里,小H需要将一个长度为n的非负整数序列分割成k+1个非空的子序列。为了得到k+1个子序列,小H需要重复k次以下的步骤:
1.小H首先选择一个长度超过1的序列(一开始小H只有一个长度为n的序列——也就是一开始得到的整个序列);
2.选择一个位置,并通过这个位置将这个序列分割成连续的两个非空的新序列。
每次进行上述步骤之后,小H将会得到一定的分数。这个分数为两个新序列中元素和的乘积。小H希望选择一种最佳的分割方式,使得k轮之后,小H的总得分最大。
Input
输入第一行包含两个整数n,k(k+1≤n)。
第二行包含n个非负整数a1,a2,...,an(0≤ai≤10^4),表示一开始小H得到的序列。
Output
输出第一行包含一个整数,为小H可以得到的最大分数。
Sample Input
7 3
4 1 3 4 0 2 3
4 1 3 4 0 2 3
Sample Output
108
思路:
Dp,斜率优化。
首先考虑正常的方程F[i][j] 表示在前i个点切j次,在i点切最后一次。首先要思考好如果切割方案一样,切割顺序和答案没有关系,这样我们就可以比较容易的得出转移方程:f[i][j] = max(f[k][j-1]+(s[i]-s[j])*(s[n]-s[i])); 考虑斜率优化,这样的状态我们显然是无法斜率优化的,所以尝试把第二维拉到前面 这样
f[i][j] 就只和f[i-1][k]有关,可以把第一维压掉,进行斜率优化,设g[j]为滚动数组,则新的转移方程为 :f[i] =max(g[j]+(s[i]-s[j])*(s[n]-s[i]));化简开得f[i] + si*si - si*sn = g[j] - sjsn +sisj; 这样把和i有关的设为Y,和j有关的设为b,把sj设为k,si即为x。Y = kx+b;
因为s单调递增 所以各种单调递增。
考虑对队首的维护,若Y(j,q[l]) < Y(j,q[l+1])弹出队首
考虑对队尾的维护,当队尾第二个元素与j构成的斜率小于队尾斜率时,弹出队尾。
其余的直接更新即可。
把方程化成Y = KX + B的形式免去了斜率优化中的除法。常数较小
以下是代码
=#include <cstdio> #include <cstring> using namespace std; #define ll long long const int N = 100005; #define K(i) (s[i]) #define B(i) (g[i]-s[i]*s[i]) #define Y(i,j) (K(j)*s[i]+B(j)) int q[N],l,r; ll g[N],f[N],s[N]; bool cmp(int i,int j,int k) { ll x=(K(i)-K(k))*(B(j)-B(i)); ll y=(K(i)-K(j))*(B(k)-B(i)); return x>=y; } int main() { int n,k,i,x,j; scanf("%d%d",&n,&k); for(i=1;i<=n;i++) { scanf("%d",&x); s[i]=s[i-1]+x; } for(i=1;i<=k;i++) { l=r=0; for(j=i;j<=n;j++) { while(l<r&&Y(j,q[l])<Y(j,q[l+1]))l++; f[j]=Y(j,q[l]); while(l<r&&cmp(j,q[r-1],q[r]))r--; q[++r]=j; } for(j=i;j<=n;j++) { g[j]=f[j]; } } printf("%lld\n",f[n]); }
欢迎来原博客看看>原文链接<