题意
小H最近迷上了一个分隔序列的游戏。在这个游戏里,小H需要将一个长度为n的非负整数序列分割成k+1个非空的子序列。为了得到k+1个子序列,小H需要重复k次以下的步骤:
1.小H首先选择一个长度超过1的序列(一开始小H只有一个长度为n的序列——也就是一开始得到的整个序列);
2.选择一个位置,并通过这个位置将这个序列分割成连续的两个非空的新序列。
每次进行上述步骤之后,小H将会得到一定的分数。这个分数为两个新序列中元素和的乘积。小H希望选择一种最佳的分割方式,使得k轮之后,小H的总得分最大。
2≤n≤100000,1≤k≤min(n -1,200)。
分析
要做这题首先要知道一个很重要的结论,就是拆分的顺序是不影响答案的。
然后直接上斜率dp即可。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=100005;
int n,m,s[N],now,q[N];
LL f[2][N];
LL get1(int x,int y)
{
return f[now^1][x]-(LL)s[x]*s[x]-f[now^1][y]+(LL)s[y]*s[y];
}
LL get2(int x,int y)
{
return s[y]-s[x];
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%d",&s[i]);
if (!s[i])
{
i--;n--;
continue;
}
s[i]+=s[i-1];
}
m=min(m,n-1);
now=0;
for (int i=2;i<=m+1;i++)
{
now=1-now;
int head=1,tail=1;
q[1]=i-1;
for (int j=i;j<=n;j++)
{
while (head<tail&&get1(q[head],q[head+1])<=(LL)s[j]*get2(q[head],q[head+1])) head++;
int k=q[head];
f[now][j]=f[1-now][k]+(LL)s[k]*(s[j]-s[k]);
while (head<tail&&get1(q[tail-1],q[tail])*get2(q[tail],j)>get1(q[tail],j)*get2(q[tail-1],q[tail])) tail--;
q[++tail]=j;
}
}
printf("%lld",f[now][n]);
return 0;
}