[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; }