征途 BZOJ 4518
征途
【问题描述】
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 后的
【样例输入】
1 2 5 8 6
【样例输出】
【数据范围】
1≤n≤3000,保证从 S 到 T 的总路程不超过 30000
题解:
来推一下式子:
方差:(x1 - aver)2 + (x2 - aver)2 + ... + (xm - aver)2 / m
然后题意要求乘m2
那么
m×[(x1 - aver)2 + (x2 - aver)2 + ... + (xm - aver)2 ]
= m×[x12 + x22 + ... + xm2 - 2aver(x1 + x2 + ... + xm ) + m × aver2]
= m×(x12 + x22 + ... + xm2) - 2sum2 + sum2 (aver = sum / m)
= m×(x12 + x22 + ... + xm2) - sum2
其实m和sum都为常量,那么只要考虑中间的平方和部分
设f[i][j]为分到点j且分成i段时每一段的平方和
转移方程即为:f[i][j] = min(f[i][j], f[i - 1][k] + (sum[j] - sum[k]) * (sum[j] - sum[k])); (k < j)
三方效率肯定过不了,看出这是一个斜率优化的裸题,那就可以虾搞蛋了~\(≧▽≦)/~
1 #include<algorithm>
2 #include<iostream>
3 #include<cstring>
4 #include<cstdlib>
5 #include<cstdio>
6 #include<cmath>
7 using namespace std;
8 inline int Get()
9 {
10 int x = 0;
11 char c = getchar();
12 while('0' > c || c > '9') c = getchar();
13 while('0' <= c && c <= '9')
14 {
15 x = (x << 3) + (x << 1) + c - '0';
16 c = getchar();
17 }
18 return x;
19 }
20 int n, m;
21 int t, w;
22 int c[10233];
23 int s[10233];
24 long long aver;
25 long long f[3233][3233];
26 long long sum[10233];
27 double Up(int x, int y, int i)
28 {
29 return f[i - 1][x] + sum[x] * sum[x] - f[i - 1][y] - sum[y] * sum[y];
30 }
31 double Down(int x, int y)
32 {
33 return (sum[x] - sum[y]) << 1;
34 }
35 long long Dp(int i, int j, int x)
36 {
37 return f[i - 1][x] + (sum[j] - sum[x]) * (sum[j] - sum[x]);
38 }
39 int main()
40 {
41 scanf("%d%d", &n, &m);
42 for(int i = 1; i <= m; ++i)
43 for(int j = 1; j <= n; ++j)
44 f[i][j] = 214748364721474836LL;
45 for(int i = 1; i <= n; ++i)
46 {
47 scanf("%d", &c[i]);
48 sum[i] = sum[i - 1] + c[i];
49 f[1][i] = sum[i] * sum[i];
50 }
51 aver = sum[n];
52 for(int i = 2; i <= m; ++i)
53 {
54 t = 1, w = 0;
55 s[++w] = i - 1;
56 for(int j = i; j <= n; ++j)
57 {
58 /*
59 for(int k = i - 1; k <= j; ++k)
60 f[i][j] = min(f[i][j], f[i - 1][k] + (sum[j] - sum[k]) * (sum[j] - sum[k]));
61 */
62 while(t < w && Up(s[t], s[t + 1], i) / Down(s[t], s[t + 1]) <= sum[j]) ++t;
63 f[i][j] = Dp(i, j, s[t]);
64 while(t < w && Up(j, s[w], i) / Down(j, s[w]) <= Up(s[w], s[w - 1], i) / Down(s[w], s[w - 1])) --w;
65 s[++w] = j;
66 }
67 }
68 printf("%lld", (long long) m * f[m][n] - aver * aver);
69 }