bzoj4518 [Sdoi2016]征途
原题地址:http://www.lydsy.com/JudgeOnline/problem.php?id=4518
题意:
从S地到T地的路可以划分成n段,相邻两段路的分界点设有休息站。
Pine计划用m天到达T地。除第m天外,每一天晚上Pine都必须在休息站过夜。所以,一段路必须在同一天中走完。
Pine希望每一天走的路长度尽可能相近,所以他希望每一天走的路的长度的方差尽可能小。
帮助Pine求出最小方差是多少。
设方差是v,可以证明,v×m^2是一个整数。为了避免精度误差,输出结果时输出v×m^2。
数据范围
1≤n≤3000,保证从 S 到 T 的总路程不超过 30000
题解:
如果设每一天的旅程分别是
t1,t2...tm
,每一段路的前缀和分别是
s1,s2...sn
ans=m2∗1m∑mi=1(ti−snm)2
=m∑mi=1t2i+s2n−2sn∑ti
=m∑mi=1t2i−s2n
dp[i][k] 表示前i段路分成k段 ∑t2i 的最小值。
dp[i][k]=min(dp[j][k−1]+(si−sj)2)1≤k<=j
(可以一天不走路)
dp[i][k]=dp[j][k−1]+(si−sj)2
dp[i][k]+2sisj=dp[j][k−1]−s2j+s2i
又是截距的形式。
求min值,下凸壳,
x=2sj
单增,
k=2si
单增,直接用队列维护即可。
注意:可以一天不走路,于是转移时注意一下,或者每个k的f[n]取个max。
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define LL long long
using namespace std;
const int N=3005;
const LL inf=1e15;
int n,m,Q[N];
LL f[N],g[N],s[N];
LL cal(int x,int i) {return f[x]+s[x]*s[x]-2*s[i]*s[x];}
long double slope(int x,int y) {return (long double) (f[y]-f[x]+s[y]*s[y]-s[x]*s[x])/(s[y]-s[x]);}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) {scanf("%lld",&s[i]); s[i]+=s[i-1];}
for(int i=1;i<=n;i++) f[i]=inf; f[0]=0; LL ret=inf;
for(int k=1;k<=m;k++)
{
int h=1; int t=0;
for(int i=0;i<=n;i++)
{
while(h<t&&cal(Q[h],i)>=cal(Q[h+1],i)) h++;
if(h>t) g[i]=inf;
else g[i]=cal(Q[h],i)+s[i]*s[i];
while(h<t&&slope(Q[t-1],Q[t])>=slope(Q[t-1],i)) t--;
t++; Q[t]=i;
}
for(int i=0;i<=n;i++) f[i]=g[i]; ret=min(ret,f[n]);
}
ret=1LL*m*ret-s[n]*s[n];
printf("%lld\n",ret);
return 0;
}