[Sdoi2016]征途

4518: [Sdoi2016]征途

Time Limit: 10 Sec  Memory Limit: 256 MB
[Submit][Status][Discuss]

Description

Pine开始了从S地到T地的征途。
从S地到T地的路可以划分成n段,相邻两段路的分界点设有休息站。
Pine计划用m天到达T地。除第m天外,每一天晚上Pine都必须在休息站过夜。所以,一段路必须在同一天中走完。
Pine希望每一天走的路长度尽可能相近,所以他希望每一天走的路的长度的方差尽可能小。
帮助Pine求出最小方差是多少。
设方差是v,可以证明,v×m^2是一个整数。为了避免精度误差,输出结果时输出v×m^2。
 

Input

第一行两个数 n、m。
第二行 n 个数,表示 n 段路的长度
 

Output

 一个数,最小方差乘以 m^2 后的值

 

Sample Input

5 2
1 2 5 8 6

Sample Output

36

HINT

1≤n≤3000,保证从 S 到 T 的总路程不超过 30000

x[i]表示第i天走的距离,X表示x[i]的平均值=(x[1]+x[2]+……x[m])/m

m^2 中把1个m 和 算方差的 除m 抵消

ans=min 【(X-x[1])^2 + (X-x[2])^2 + ……(X- x[m])^2 】*m

      = m*【m*X^2 + (x[1]^2+x[2]^2+……x[m]^2) - 2*X*(x[1]+x[2]+……x[m])】

      = m*(x[1]^2+x[2]^2+……x[m]^2)- (x[1]+x[2]+……x[m])^2

只需要DP维护 (x[1]^2+x[2]^2+……x[m]^2)的最小值即可

斜率优化

#include<cstdio>
#include<algorithm>
#define N 3001
using namespace std;
int a[N];
long long sum[N],now[N],last[N];
int q[N],head,tail;
long long up(int k,int j)
{
    return last[j]-last[k]+sum[j]*sum[j]-sum[k]*sum[k];
}
long long down(int k,int j)
{
    return 2*(sum[j]-sum[k]);
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        sum[i]=sum[i-1]+a[i];
    }
    for(int i=1;i<=n;i++) last[i]=sum[i]*sum[i];
    for(int k=2;k<=m;k++)
    {
        head=tail=1; q[1]=k-1;
        for(int i=k;i<=n;i++)
        {
            while(head<tail && up(q[head],q[head+1])<=down(q[head],q[head+1])*sum[i]) head++;
            now[i]=last[q[head]]+(sum[i]-sum[q[head]])*(sum[i]-sum[q[head]]);
            while(head<tail && up(q[tail-1],q[tail])*down(q[tail],i)>=up(q[tail],i)*down(q[tail-1],q[tail])) tail--;
            q[++tail]=i;
        } 
        swap(now,last);
    }
    printf("%lld",m*last[n]-sum[n]*sum[n]);
}
原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/7483418.html