zoukankan      html  css  js  c++  java
  • 征途[SDOI2016]

    题目描述

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

    (S) 地到 (T) 地的路可以划分成 (h) 段,相邻两段路的分界点设有休息站。
    Pine 计划用 (m) 天到达 (T) 地。除第 (n) 天外,每一天晚上 Pine 都必须在休息站过夜。所以,一段路必须在同一天中走完。
    Pine 希望每一天走的路长度尽可能相近,所以他希望每一天走的路的长度的方差尽可能小。

    帮助 Pine 求出最小方差是多少。

    设方差是 (v) ,可以证明,(v*m^2) 是一个整数。为了避免精度误差,输出结果时输出 (v*m^2)

    输入格式

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

    题解

    CCF不考DP了(悲)

    假设有一个序列 (a_1sim a_n) ,平均数是 (v) ,那么它的方差就是 (dfrac{sumlimits_{i=1}^{n}(a_i)^2}{n}-v^2)

    所以原问题就变成:将 (n) 个数分为 (m) 段,假设第 (i) 段内的所有数之和为 (b_i) ,求 (sumlimits_{i=1}^{m} (b_i)^2) 的最小值

    (f_{i,j}) 表示将前 (j) 个数分成 (i) 段的最小平方和

    容易得到转移方程: (f_{i,j} = minlimits_{0le k<j} (f_{i-1,k} + (S_j-S_k)^2))(S) 表示前缀和

    变换一下得到 (f_{i-1,k}+(S_k)^2=2*S_j*S_k+(f_{i,j}-(S_j)^2))

    这样就可以进行斜率优化,维护 (f_{i-1}) 的凸壳来转移出 (f_i)

    由于 (S) 数组单调递增,所以直接用单调队列维护下凸壳即可

    时间复杂度 (O(nm))

    代码

    #include <bits/stdc++.h>
    #define N 5005
    using namespace std;
    typedef long long ll;
    
    template<typename T>
    inline void read(T &num) {
    	T x = 0, f = 1; char ch = getchar();
    	for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') f = -1;
    	for (; ch <= '9' && ch >= '0'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ '0');
    	num = x * f;
    }
    
    int n, m, head, tail, q[N];
    ll a[N], f[2][N], x[N], y[N];
    
    inline double slope(int p, int q) {
    	if (x[p] == x[q]) return 1e15;
    	else return 1.0 * (y[p] - y[q]) / (x[p] - x[q]);
    }
    
    int main() {
    	read(n); read(m);
    	for (int i = 1; i <= n; i++) {
    		read(a[i]); a[i] += a[i-1]; 
    	}
    	int o = 0; 
    	for (int i = 1; i <= m; i++) {
    		o = i & 1;
    		head = 1; tail = 0;
    		for (int j = 0; j <= n; j++) {
    			if (i > 1 || j == 0) {
    				while (head < tail && slope(q[tail-1], q[tail]) > slope(q[tail], j)) tail--;
    				q[++tail] = j;
    			}
    			while (head < tail && slope(q[head], q[head+1]) < (double)a[j]) head++;
    			int k = q[head];
    			f[o][j] = f[!o][k] + (a[j]-a[k]) * (a[j]-a[k]);
    		}
    		for (int j = 0; j <= n; j++) {
    			f[!o][j] = 0;
    			x[j] = 2 * a[j]; y[j] = f[o][j] + a[j] * a[j];
    		}
    	}
    	printf("%lld
    ", f[m&1][n] * m - a[n] * a[n]);
    	return 0;
    }
    
  • 相关阅读:
    企业网盘针对文件权限管理的技术实现
    学校私有云盘在学校信息化建设中的作用-教学资源库平台
    Mobox 知识管理平台助推市长质量奖
    为勇敢的华裔女子点赞
    一群喵星人把他家包围了。。
    【OI】简单的分块
    【OI】Kruskal & ufs (克鲁斯卡与并查集)
    【OI】向量&矩阵乘法
    【OI】同余方程
    【Ubuntu】某灯图标过大
  • 原文地址:https://www.cnblogs.com/ak-dream/p/AK_DREAM115.html
Copyright © 2011-2022 走看看