Placing Lampposts UVA - 10859 放置街燈 樹形dp

As a part of the mission ‘Beautification of Dhaka City’, the government has decided to replace all the old lampposts with new expensive ones. Since the new ones are quite expensive and the budget is not up to the requirement, the government has decided to buy the minimum number of lampposts required to light the whole city.

      Dhaka city can be modeled as an undirected graph with no cycles, multi-edges or loops. There are several roads and junctions. A lamppost can only be placed on junctions. These lampposts can emit light in all the directions, and that means a lamppost that is placed in a junction will light all the roads leading away from it.

     The ‘Dhaka City Corporation’ has given you the road map of Dhaka city. You are hired to find the minimum number of lampposts that will be required to light the whole city. These lampposts can then be placed on the required junctions to provide the service. There could be many combinations of placing these lampposts that will cover all the roads. In that case, you have to place them in such a way that the number of roads receiving light from two lampposts is maximized. 

分析:設a表示放置燈數(儘量小),一盞燈照亮的邊數爲c(儘量小),用x = Ma+b表示最小化目標,其中M是一個很大的正整數(M比a的最大理論值和c的最小理論值之差還要大),則 a = x/M, b = x%M,主要連個優化條件就變成了一個。

設d(i,j)表示以i爲根節點的子樹的最小值x。j表示i的父節點是否放燈(1放,0沒放)。

【分析】
無向無環圖的另一個說法是“森林”, 它由多棵樹組成。 動態規劃是解決樹上的優化問題的常用工具, 本題就是一個很好的例子。首先, 本題的優化目標有兩個: 放置的燈數a應儘量少, 被兩盞燈同時照亮的邊數b應儘量大。 爲了統一起見, 我們把後者替換爲: 恰好被一盞燈照亮的邊數c應儘量小, 然後改用x=Ma+c作爲最小化的目標, 其中M是一個很大的正整數。 當x取到最小值時,x/M的整數部分就是放置的燈數的最小值; x%M就是恰好被一盞燈照亮的邊數的最小值。
一般來說, 如果有兩個需要優化的量v1和v2, 要求首先滿足v1最小,在v1相同的情況下v2最小, 則可以把二者組合成一個量Mv1+v2, 其中M是一個比“v2的最大理論值和v2的最小理論值之差”還要大的數。 這樣, 只要
兩個解的v1不同, 則不管v2相差多少, 都是v1起到決定性作用; 只有當v1相同時, 才取決於v2。

在本題中, 可以取M=2000。每棵樹的街燈互不相干, 因此可以單獨優化, 最後再把答案加起來即可。 下面我們只考慮一棵樹的情況。 首先對這棵樹進行DFS, 把無根樹轉化爲有根樹, 然後試着設狀態d(i) 爲以i爲根的子樹的最小x值, 看看能不能寫出狀態轉移方程。
決策只有兩種: 在i處放燈和不在i處放燈。 後繼狀態是i的各個子結點。 可是問題來了: i處是否放燈將影響到其子結點的決策。 因此, 我們需要把“父結點處有沒有放燈”加入狀態表示中。 新狀態爲:d(i,j) 表示i的父結點“是否放燈”的值爲j(1表示放燈,0表示沒放) , 以i爲根的樹的最小x值(算上i和其父結點這條邊) 。
注意到各子樹可以獨立決策, 因此可作出如下決策。
決策一: 結點i不放燈。 必須j=1或者i是根結點時才允許作這個決123策。 此時d(i,j) 等於sum{d(k,0) |k取遍i的所有子結點} 。 如果i不是根, 還得加上1, 因爲結點i和其父結點這條邊上只有一盞燈照亮。
決策二: 結點i放燈。 此時d(i,j) 等於sum{d(k,1) |k取遍i的所有子結點} +M。 如果j=0且i不是根, 還得加上1, 因爲結點i和其父結點這條邊只有一盞燈照亮。用數學式子很難表達上面的狀態轉移, 但用程序表達卻可以很清晰

#include <cstdio>
#include <vector>
#include <cstring>
using namespace std; 
const int N = 1000+5, M = 2000;
vector<int> g[N];  // 森林是稀疏的,這樣保存省空間,枚舉相鄰結點也更快
int d[N][2], vis[N][2];
// 在DFS的同時進行動態規劃,f是i的父結點,它不存入狀態裏
int dp(int i,int j, int f){
	if(vis[i][j]) return d[i][j];
	vis[i][j] = 1;
	int &ans = d[i][j];
	 // 森林是稀疏的,這樣保存省空間,枚舉相鄰結點也更快
	ans = M; //// 燈的數量加1,x加M
	for(int k = 0; k < g[i].size(); k++)
		if(g[i][k] != f)
			ans += dp(g[i][k], 1, i); 
	if(!j && f >= 0) ans++; // 如果i不是根,且父結點沒放燈(即父節點只有一盞燈),則x加1	
	// i是根或者其父結點已放燈,i纔可以不放燈
	if(f < 0 || j == 1){ 
		int sum = 0;
		for(int k = 0; k < g[i].size(); k++)
			if(g[i][k] != f)
				sum += dp(g[i][k], 0, i);
		if(f >= 0) sum++; // 如果i不是根(即i只有一盞燈),則x加1
		ans = min(ans, sum);
	}	
	return ans;
}
int main(int argc, char** argv) {
	int T;
	scanf("%d",&T);
	while(T--){
		int n, m;
		scanf("%d%d",&n,&m);
		for(int i = 0; i < n; i++) // g裏保存着上一組數據的值,必須清空 
			g[i].clear(); 
		for(int i = 0; i < m; i++){
			int u, v;
			scanf("%d%d",&u,&v);
			g[u].push_back(v);
			g[v].push_back(u);
		}
		int ans = 0;
		memset(vis, 0, sizeof(vis));
		for(int i = 0; i < n; i++) 
			if(!vis[i][0]) // 新的一棵樹
				ans += dp(i, 0, -1); // i是樹根,因此父結點不存在(-1)	
		printf("%d %d %d\n", ans/M, m-ans%M, ans%M); //從x計算3個整數
	} 
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章