题意:
给你一个长度为
n
n
n的序列,你要把它切
k
k
k次分为
k
+
1
k+1
k+1段,每次切开一块之后对答案的贡献是新的两个块的权值和的乘积。你现在要输出切
k
k
k次后最大的权值和,洛谷上还要求输出分段的位置。
题解:
我们化一下式子会发现,你确定了所有要切的位置之后,我们会发现,切这些位置的先后顺序对答案没有影响。证明的话好像可以用个类似分配律的东西化一下吧。
我们考虑dp,我们设 d p [ i ] [ k ] dp[i][k] dp[i][k]表示前 i i i个位置,切了 k k k次,并且第 k k k次切的位置是 i i i的最大答案。我们有 d p [ i ] [ k ] = m a x ( d p [ j ] [ k − 1 ] + p r e [ j ] ∗ ( p r e [ i ] − p r e [ j ] ) ) dp[i][k]=max(dp[j][k-1]+pre[j]*(pre[i]-pre[j])) dp[i][k]=max(dp[j][k−1]+pre[j]∗(pre[i]−pre[j]))。其中 p r e [ i ] pre[i] pre[i]表示前 i i i个数的权值前缀和,这个式子理解一下的话,可以看作虽然是从前向后转移,但是切的时候先切后面位置再切前面位置的,然后因为切的顺序与答案无关,我们任意的顺序都是等价的,这个顺序比较好算答案,就用了这个顺序。
然后我们发现前面那个式子可以斜率优化,再加上一个滚动数组,我们就可以时间空间复杂度都是 O ( n ) O(n) O(n)的了。然后至于洛谷的输出方案的话,可能就记录一下每个状态转以来的上一个位置在哪就可以了,但是空间是 O ( n k ) O(nk) O(nk)的了。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,k,q[2000010],l,r,opt,ji[210][1000010],gg;
long long a[1000010],dp[1000010][2],s[1000010],ans;
inline int read()
{
int x=0;
char s=getchar();
while(s>'9'||s<'0')
s=getchar();
while(s>='0'&&s<='9')
{
x=x*10+s-'0';
s=getchar();
}
return x;
}
inline double slop(int x,int y)
{
if(s[x]==s[y])
return -1e9;
return (double)(s[y]*s[y]-dp[y][opt^1]-(s[x]*s[x]-dp[x][opt^1]))/(double)(s[y]-s[x]);
}
int main()
{
n=read();
k=read();
for(int i=1;i<=n;++i)
a[i]=read();
for(int i=1;i<=n;++i)
s[i]=s[i-1]+a[i];
for(int i=1;i<=k;++i)
{
l=0;
r=0;
opt=i&1;
for(int j=1;j<=n;++j)
{
while(l<r&&slop(q[l+1],q[l])<s[j])
++l;
ji[i][j]=q[l];
dp[j][opt]=dp[q[l]][opt^1]+s[j]*s[q[l]]-s[q[l]]*s[q[l]];
while(l<r&&slop(q[r-1],q[r])>slop(q[r],j))
--r;
q[++r]=j;
}
}
for(int i=1;i<=n;++i)
{
if(ans<dp[i][opt])
{
ans=dp[i][opt];
gg=i;
}
}
printf("%lld\n",ans);
for(int i=k;i>=1;--i)
{
gg=ji[i][gg];
printf("%d ",gg);
}
return 0;
}