1. 程式人生 > 實用技巧 >洛谷P5929 [POI1999]地圖

洛谷P5929 [POI1999]地圖

洛谷題目連結

零、題目原文

一個人口統計辦公室要繪製一張地圖。

由於技術的原因只能使用少量的顏色。兩個有相同或相近人口的區域在地圖應用相同的顏色。例如一種顏色 \(k\),則 \(A(k)\) 是相應的數,則有:

在用顏色\(k\) 的區域中至少有一半的區域的人口不大於 \(A(k)\)

在用顏色\(k\) 的區域中至少有一半的區域的人口不小於 \(A(k)\)

區域顏色誤差是該區域的人口與 \(A(k)\) 差的絕對值。累計誤差是所有區域顏色誤差的總和。我們要求出一種最佳的染色方案,使得累計誤差最小。

\(10 < n < 3000\)\(2 ≤ m ≤ 10\)
人口數 \(< 2^{30}\)

一、簡明題意

\(n\) 個區域,每個區域人口是\(a[i]\)\(m\) 種顏色,現在要對所有區域著色。

\(A(k)\) 即是中位數。(因為有一半區域不大於或不小於,所以肯定不大於與不小於的個數相等,即為中位數)

區間誤差即為連續同樣顏色區間的每個數與中位數的絕對值之和,累計誤差即為所有區間誤差之和。

二、思路

1、樸素轉移

這題題目都講了區間,明顯的區間DP......

\(f[i][j]\) 表示考慮了前\(i\) 個區域,已經用了\(j\) 種顏色的最小誤差

則列舉 \(k\), \(f[i][j]\) 一定是從前k個區間的最小值再加上從\(k+1\)\(i\) 的區間誤差,

狀態轉移方程式:\(f[i][j] = min(f[i][j], f[k][j - 1] + color(k + 1, i))\) ,其中\(color\) 即為區間誤差之和。

時間複雜度:\(O(N ^ 3M)\)

很明顯,這個巨大的複雜度會T飛,所以需要一些優化

2、求值優化

再思考優化\(color\) ,可以先字首和\(sum\) 求出每一段的人口數,而中位數即為排好序後中間兩個數的平均數。

所以區間誤差之和不難得出,可以得到以下\(O(1)\) 的柿子:

\(a[mid] * (mid - l) - sum[mid - 1] + sum[l - 1] - a[mid] * (r - mid) + sum[r] - sum[mid]\)

,(其中mid即為中間那個位置,不是中位數!)

所以可以把這一個柿子單獨寫個函式拉出來,可以將時間複雜度優化到\(O(N ^ 2M)\)

三、code

注意開始需要先排序,這樣求中位數可以優化。

#include <bits/stdc++.h>
using namespace std;
const int N = 3005;
const int M = 15;
int n, m;
int f[N][M], sum[N], a[N];
int color(int l, int r)
{
	int mid = (l + r) / 2;//求中位數的位置
	return a[mid] * (mid - l) - sum[mid - 1] + sum[l - 1] - a[mid] * (r - mid) + sum[r] - sum[mid]; //O(1)優化
}              
int main()
{
	cin >> n >> m;
	for(int i = 1; i <= n; i++)
	{
		cin >> a[i];
	}
	sort(a + 1, a + n + 1);
	for(int i = 1; i <= n; i++)
	{
		sum[i] = sum[i - 1] + a[i];//字首和預處理
	}
	memset(f, 0x3f, sizeof f);//開始全賦最大值
	f[0][0] = 0;
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= m; j++)
		{
			for(int k = 0; k < i; k++)
			{
				f[i][j] = min(f[i][j], f[k][j - 1] + color(k + 1, i));//轉移方程
			}
		}		
	}
	cout << f[n][m];
	return 0;
}