Description
给定一个长度为 (n) 的序列,需要将其划分成 (k+1) 份,每次划分时会将一个段划分成两个段,本次操作的得分为划分成的两个段的和的乘积。求最大得分和为多少。
Solution
容易证明操作顺序对答案没有影响,具体可以证明个结合律,代数系统里面有过类似的东西。
设 (f(i,j)) 表示前 (j) 个数分了 (i) 次的最大得分,则显然有转移
[f(i,j) leftarrow f(i-1,k) + s_k (s_j-s_k)
]
转化一下,并记 (f_j = f(i,j), g_j = f(i-1,j)),则有
[s_k^2 - g_k = s_j s_k - f_j
]
于是,考虑设 (Y(k) = s_k^2 - g_k , K(j) = s_j, X(k) = s_k, B(j) = -f_j),斜率优化即可,维护一个斜率单调递增的单调队列。
自闭原因:数组大小写反,卡精度
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define double long double
const int N = 100005;
const int M = 205;
int n,k,a[N],s[N],q[N],f[N],g[N],head=0,tail=0;
int from[M][N];
double Y(int k)
{
return s[k]*s[k]-g[k];
}
double X(int k)
{
return s[k];
}
double K(int j)
{
return s[j];
}
double B(double Y,double K,double X)
{
return Y-K*X;
}
double slope(int p,int q)
{
double dy=Y(p)-Y(q);
double dx=X(p)-X(q);
if(abs(dx)<1e-9)
{
return -1e32;
}
else
{
return dy/dx;
}
}
signed main()
{
// freopen("p3648.in","r",stdin);
ios::sync_with_stdio(false);
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i];
for(int i=1;i<=k;i++)
{
for(int j=1;j<=n;j++) g[j]=f[j];
head=0;
tail=-1;
q[0]=0;
for(int j=1;j<=n;j++)
{
while(head<tail && slope(q[head],q[head+1])<=K(j)) ++head;
if(head<=tail)
{
int k=q[head];
f[j]=g[k]+s[k]*(s[j]-s[k]);
from[i][j]=k;
}
else f[j]=0;
while(head<tail && slope(q[tail-1],q[tail])>=slope(q[tail],j)) --tail;
q[++tail]=j;
}
}
cout<<f[n]<<endl;
int i=k, j=from[k][n];
while(i)
{
cout<<j<<" ";
j=from[--i][j];
}
}