[BZOJ4518][Sdoi2016]征途

[BZOJ4518][Sdoi2016]征途

试题描述

Pine开始了从S地到T地的征途。

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

输入

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

输出

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

输入示例

5 2
1 2 5 8 6

输出示例

36

数据规模及约定

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

题解

设 f(i, k) 表示考虑前 i 段路分成 k 次的最小方差。则 f(i, k) = min{ f(j, k-1) + (Si - Sj - x)2 },其中 Si 为前 i 段路的总长,x = Sn / m(想一想为什么)。

开一个滚动数组然后斜率优化就好了(做法类似http://www.cnblogs.com/xiao-ju-ruo-xjr/p/5149405.html这篇博客中“[bzoj3675][Apio2014]序列分割”这道题的做法,不会的话可以看看那道题,还能得双倍经验= =),这里就不推式子了。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

int read() {
	int x = 0, f = 1; char tc = getchar();
	while(!isdigit(tc)){ tc = getchar(); if(tc == '-') f = -1; }
	while(isdigit(tc)){ x = x * 10 + tc - '0'; tc = getchar(); }
	return x * f;
}

#define maxn 3010
#define oo 2147483647
#define LL long long
int n, m, cur, Q[maxn];
LL S[maxn], f[maxn][2];

struct Point { LL x, y; } ps[maxn];
double slop(int a, int b) { return ((double)ps[a].y - ps[b].y) / ((double)ps[a].x - ps[b].x); }

int main() {
	n = read(); m = read();
	for(int i = 1; i <= n; i++) S[i] = S[i-1] + read();
	
	for(int i = 1; i <= n; i++) f[1][i] = oo;
	for(int j = 1; j <= m; j++) {
		int l = 1, r = 0; Q[++r] = 0; ps[0] = (Point){ 0, 0 };
		for(int i = 1; i <= n; i++) {
			while(l < r && 2.0 * (double)m * S[i] >= slop(Q[l], Q[l+1])) l++;
			int k = Q[l];
			f[i][cur] = f[k][cur^1] + (LL)m * S[k] * S[k] + 2ll * S[k] * S[n] - 2ll * m * S[i] * S[k] + (LL)m * S[i] * S[i] - 2ll * S[i] * S[n];
			ps[i] = (Point){ S[i], f[i][cur^1] + (LL)m * S[i] * S[i] + 2ll * S[i] * S[n] };
			while(l < r && slop(Q[r-1], Q[r]) >= slop(Q[r], i)) r--;
			Q[++r] = i;
		}
		cur ^= 1;
	}
	
	printf("%lld\n", f[n][cur^1] + S[n] * S[n]);
	
	return 0;
}

 

posted @ 2016-06-02 20:00  xjr01  阅读(247)  评论(0编辑  收藏  举报