BZOJ2933 [Poi1999]地圖【區間DP】
阿新 • • 發佈:2018-11-10
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; }