1. 程式人生 > >(樹形DP+揹包)POJ1947Rebuilding Roads

(樹形DP+揹包)POJ1947Rebuilding Roads

Rebuilding Roads

Time Limit: 1000MS

Memory Limit: 30000K

Total Submissions: 13307

Accepted: 6171

Description

The cows have reconstructed Farmer John's farm, with its N barns (1 <= N <= 150, number 1..N) after the terrible earthquake last May. The cows didn't have time to rebuild any extra roads, so now there is exactly one way to get from any given barn to any other barn. Thus, the farm transportation system can be represented as a tree.  Farmer John wants to know how much damage another earthquake could do. He wants to know the minimum number of roads whose destruction would isolate a subtree of exactly P (1 <= P <= N) barns from the rest of the barns.

Input

* Line 1: Two integers, N and P  * Lines 2..N: N-1 lines, each with two integers I and J. Node I is node J's parent in the tree of roads. 

Output

A single line containing the integer that is the minimum number of roads that need to be destroyed for a subtree of P nodes to be isolated. 

Sample Input

11 6

1 2

1 3

1 4

1 5

2 6

2 7

2 8

4 9

4 10

4 11

Sample Output

2

Hint

[A subtree with nodes (1, 2, 3, 6, 7, 8) will become isolated if roads 1-4 and 1-5 are destroyed.] 

Source

程式碼實現:

題意:

將一棵n個節點的有根樹,刪掉一些邊變成恰有m個節點的新樹。求最少需要去掉幾條邊。

分析:

定義狀態dp(root,k)表示在以root為根節點的子樹中,刪掉一些邊變成恰有k個節點的新樹需要刪去的最少邊數。

對於根節點root的某個兒子son

要麼將son及其所有的子節點全部刪掉,則dp(root,k)=dp(root,k)+1,只需刪除rootson之間的邊;

要麼在son的子樹中選出一些邊刪掉,構造出有j個節點的子樹,狀態轉移方程為dp(root,k)=max(dp(root,k),dp(son,j)+dp(root,k-j))

演算法分析:

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <stack>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
const int N=200;
struct node
{
	int v;///終端點
    int next;///下一條同樣起點的邊號
    int w;///權值
}edge[N*2];///無向邊,2倍
int head[N];///head[u]=i表示以u為起點的所有邊中的第一條邊是 i號邊
int tot;  ///總邊數
int minn;
int fa[N];
void add(int u,int v)
{
	edge[tot].v=v;
	//edge[tot].w=w;
    edge[tot].next=head[u];
    head[u]=tot++;
}
int n,m;
int dp[N][N];
int dfs(int u,int fa) 
{
	int num=0;     ///記錄u的結點數
	for(int i = 0; i <= m; i++) ///初始化為無窮大
	  dp[u][i] = 1e8;
	  
	dp[u][1]=0;        ///根結點本就一個點,不需要減邊
	for(int i=head[u];i!=-1;i=edge[i].next)
	 {
	 	int v= edge[i].v;
	    if(fa==v) continue;   ///如果下一個相鄰節點就是父節點,則證明到底層了,開始遞迴父節點的兄弟節點
		
		num=dfs(v,u)+1;
		for(int j=m;j>0;j--)
			for(int k=0;k<j;k++)   ///列舉子節點保留的個數
		{
		 if(k)     ///不加子樹k
		   dp[u][j]=min(dp[u][j],dp[u][j-k]+dp[v][k]);	
		 else    ///加子樹k
		   dp[u][j]=dp[u][j]+1;
		}
	 }
	 return num;
}
int main()
{
  	scanf("%d%d",&n,&m);
  	memset(head,-1,sizeof(head));
    memset(dp,0,sizeof(dp));
    memset(fa,-1,sizeof(fa));
  	tot=0;
  	
  	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
		fa[v]=u;
	}
	int root=1;
	for(root=1;fa[root]!=-1;root=fa[root]);//尋找根節點
	
  	dfs(root,-1);
	int ans = dp[root][m];   
	for(int i = 1; i <= n; i++)   ///除了根節點,其他節點要想成為獨立的根,必先與父節點斷絕關係,所以要先加1  
	ans = min(ans, dp[i][m] + 1);  
	printf("%d\n",ans);  
    return 0;
}