背景:
逃掉了物理竞赛,有愧于班主任啊。
大概写完这一篇就
b
l
o
g
5
blog5
blog5了,暗喜…
题目传送门:
https://www.luogu.org/problemnew/show/P3648
题意:
n
n
n个数,数的值是
a
i
a_i
ai,分成
m
m
m组,其中每一组编号要连续,某一组(第
k
k
k组)(
[
l
,
r
]
[l,r]
[l,r])的贡献是
x
k
=
∑
i
=
l
r
a
i
x_k=\sum^{r}_{i=l}{a_i}
xk=∑i=lrai,总贡献的计算公式是
∑
i
=
1
m
x
i
∗
x
i
−
1
\sum_{i=1}^{m}{x_i*x_{i-1}}
∑i=1mxi∗xi−1.求最大贡献。
思路:
有点像这一题。
考虑常规做法,设
f
i
,
l
f_{i,l}
fi,l表示将前
i
i
i个元素分为
l
l
l组的最大贡献。
设
s
u
m
k
=
∑
i
=
1
k
a
i
sum_k=\sum_{i=1}^{k}a_i
sumk=∑i=1kai。
则状态转移方程为:
f
i
,
l
=
max
j
=
1
i
−
1
f
j
,
l
−
1
+
s
u
m
j
(
s
u
m
i
−
s
u
m
j
)
f_{i,l}=\max_{j=1}^{i-1}f_{j,l-1}+sum_j(sum_i-sum_j)
fi,l=maxj=1i−1fj,l−1+sumj(sumi−sumj).
得到斜率方程:
(
f
j
,
l
−
1
−
s
u
m
j
2
)
+
(
f
k
,
l
−
1
−
s
u
m
k
2
)
s
u
m
j
−
s
u
m
k
<
s
u
m
i
\frac{(f_{j,l-1}-sum_j^2)+(f_{k,l-1}-sum_k^2)}{sum_j-sum_k}<sum_i
sumj−sumk(fj,l−1−sumj2)+(fk,l−1−sumk2)<sumi
然后直接套即可。
然后就做完了。
注意一下当
s
u
m
[
x
]
=
=
s
u
m
[
y
]
sum[x]==sum[y]
sum[x]==sum[y]时的边界条件即可。
据说可以用
W
Q
S
WQS
WQS二分(凸优化),先留个坑。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
int n,m;
LL a[100010],sum[100010],f[100010][210];
int que[100010],pre[100010][210];
LL calc1(int x,int y)
{
return sum[y]-sum[x];
}
LL calc2(int x,int y,int z)
{
return (f[x][z-1]-sum[x]*sum[x])-(f[y][z-1]-sum[y]*sum[y]);
}
double calc(int x,int y,int z)
{
if(sum[x]==sum[y]) return -1e15;
return (double)(calc2(x,y,z))/(double)(calc1(x,y));
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
sum[i]=sum[i-1]+a[i];
}
for(int l=1;l<=m;l++)
{
int head=1,tail=1;
que[1]=0;
for(int i=1;i<=n;i++)
{
while(head<tail&&calc(que[head],que[head+1],l)<=(double)sum[i]) head++;
f[i][l]=f[que[head]][l-1]+sum[que[head]]*(sum[i]-sum[que[head]]);
pre[i][l]=que[head];
while(head<tail&&calc(que[tail],i,l)<=calc(que[tail-1],que[tail],l)) tail--;
que[++tail]=i;
}
}
printf("%lld\n",f[n][m]);
int tmp=n;
for(int i=m;i>=1;i--)
tmp=pre[tmp][i],printf("%d ",tmp);
}