Description
一个人口统计办公室要绘制一张地图。由于技术的原因只能使用少量的颜色。两个有相同或相近人口的区域在地图应用相同的颜色。例如一种颜色k,则A(k) 是相应的数,则有:
在用颜色k的区域中至少有一半的区域的人口不大于A(k)
在用颜色k的区域中至少有一半的区域的人口不小于A(k)
区域颜色误差是该区域的人口与A(k)差的绝对值。累计误差是所有区域颜色误差的总和。我们要求出一种最佳的染色方案(累计误差最小)。
任务
写一个程序:
读入每个区域的人口数
计算最小的累计误差
将结果输出
Input
第一行有一个整数n,表示区域数,10< n <3000。在第二行中的数m表示颜色数,2 <= m <= 10。在接下来的n中每行有一个非负整数,表示一个区域的人口。人口都不超过2^30。
Output
输出一个整数,表示最小的累计误差
Sample Input
11
3
21
14
6
18
10
2
15
12
3
2
2
Sample Output
15
思路
有贪心的思想,可以先排序,从小到大进行分块
(dp_{i,j})表示前i个数分j个颜色
然后(dp_{i,j}=min(dp_{k-1,j-1}+calc(k,i)))
(calc(k,i)=sum_{p=k}^i |a[mid]-a[p]|)
然后考虑怎么快速算calc
发现每次在端点加上一个数,中位数会向右平移一位,然而平移前后原来和是不变的
所以只需要统计当前加上这个数之后的贡献
//Author: dream_maker
#include<bits/stdc++.h>
using namespace std;
//----------------------------------------------
//typename
typedef long long ll;
//convenient for
#define fu(a, b, c) for (int a = b; a <= c; ++a)
#define fd(a, b, c) for (int a = b; a >= c; --a)
#define fv(a, b) for (int a = 0; a < (signed)b.size(); ++a)
//inf of different typename
const int INF_of_int = 1e9;
const ll INF_of_ll = 1e18;
//fast read and write
template <typename T>
void Read(T &x) {
bool w = 1;x = 0;
char c = getchar();
while (!isdigit(c) && c != '-') c = getchar();
if (c == '-') w = 0, c = getchar();
while (isdigit(c)) {
x = (x<<1) + (x<<3) + c -'0';
c = getchar();
}
if (!w) x = -x;
}
template <typename T>
void Write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) Write(x / 10);
putchar(x % 10 + '0');
}
//----------------------------------------------
const int N = 3010;
const int M = 20;
int n, m, a[N];
ll cal[N][N], dp[N][M];
int main() {
Read(n), Read(m);
fu(i, 1, n) Read(a[i]);
sort(a + 1, a + n + 1);
fu(j, 2, n)
fd(i, j - 1, 1)
cal[i][j] = cal[i + 1][j] + a[(i + j + 1) >> 1] - a[i];
fu(i, 0, n)
fu(j, 0, m) dp[i][j] = INF_of_ll;
dp[0][0] = 0;
fu(i, 1, n)
fu(j, 1, m)
fu(k, 1, i)
dp[i][j] = min(dp[i][j], dp[k - 1][j - 1] + cal[k][i]);
Write(dp[n][m]);
return 0;
}