NOI 4.6 贪心 2384: Cell Phone Network(树形DP)

题目来源:http://noi.openjudge.cn/ch0406/2384/

2384: Cell Phone Network

总时间限制10000ms    单个测试点时间限制1000ms     内存限制65536kB

描述

Farmer John has decided to give each of his cows a cell phone inhopes to encourage their social interaction. This, however, requires him to setup cell phone towers on his N (1 <= N <= 10,000) pastures (convenientlynumbered 1..N) so they can all communicate.

Exactly N-1 pairs of pastures are adjacent, and for any two pastures A and B (1<= A <= N; 1 <= B <= N; A != B) there is a sequence of adjacentpastures such that A is the first pasture in the sequence and B is the last.Farmer John can only place cell phone towers in the pastures, and each towerhas enough range to provide service to the pasture it is on and all pasturesadjacent to the pasture with the cell tower.

Help him determine the minimum number of towers he must install to provide cellphone service to each pasture.

输入

* Line 1: A single integer: N

* Lines 2..N: Each line specifies a pair of adjacent pastures with twospace-separated integers: A and B

输出

* Line 1: A single integer indicating the minimum number oftowers to install

样例输入

5
1 3
5 2
4 3
3 5

样例输出

2

提示

INPUT DETAILS:

Farmer John has 5 pastures: pastures 1 and 3 are adjacent, as are pastures 5and 2, pastures 4 and 3, and pastures 3 and 5. Geometrically, the farm lookslike this (or some similar configuration)


               4  2
               |  |
            1--3--5


OUTPUT DETAILS:

The towers can be placed at pastures 2 and 3 or pastures 3 and 5.

来源

USACO January 2008 Gold

 -----------------------------------------------------

思路

题意:求树的最小支配集,即求树的一组点集,使得树上的所有点,要么在点集之中,要么在与点集中的点相邻。

算法:树形dp(可以用贪心做的,但是没有想出来贪心的做法,反而是树形dp比较好想,但实现的过程中发现有坑-_-)

dp[u][0]: 在节点u不放基站不被覆盖的情况下以u为根的子树最少需要多少个基站

dp[u][1]: 在节点u不放基站也被覆盖的情况下以u为根的子树最少需要多少个基站

dp[u][2]: 在节点u放基站的情况下以u为根的子树最少需要多少个基站

dp[u][0] = sum_v(dp[v][1])

dp[u][1] = min(dp[v'][2] +sum_{v\v'}dp[v][1])

dp[u][2] = sum_v(min(dp[v][0],dp[v][1], dp[v][2])) + 1

其中vu的子节点

特别注意的是“在节点u不放基站也被覆盖的情况下以u为根的子树最少需要多少个基站

”(即dp[u][1])的情况,只需要所有子节点中必须保证至少有一个建了基站(dp[v’][2])就行了,但其余子节点可以只是被其他基站覆盖即可(dp[v][1]).

-----------------------------------------------------

代码 

// 树形dp
// dp[u][0]: 在节点u不放基站不被覆盖的情况下以u为根的子树最少需要多少个基站
// dp[u][1]: 在节点u不放基站也被覆盖的情况下以u为根的子树最少需要多少个基站
// dp[u][2]: 在节点u放基站的情况下以u为根的子树最少需要多少个基站
// dp[u][0] = sum_v(dp[v][1])
// dp[u][1] = min(dp[v'][2] + sum_{v\v'}dp[v][1])
// dp[u][2] = sum_v(min(dp[v][0], dp[v][1], dp[v][2])) + 1
// 其中v是u的子节点

#include<iostream>
#include<fstream>
#include<vector>
#include<cstring>
#include<climits>
using namespace std;

const int NMAX = 10005;
int vis[NMAX] = {};				// 两个功用:其一是找出根节点; 其二是判断节点是否被访问过, 因为本题的后继节点集next没有区分子节点和父节点
vector<int> adj[NMAX] = {};		// 后继节点集,不区分子节点和父节点,需要借助vis数组区分
int dp[NMAX][3] = {};			// 树形dp

void dfs(int u)
{
	int i,v,mymin,difmin=INT_MAX,dpu1copy=0;
	bool flag = 0;
	vis[u] = 1;
	bool has_son = false;
	for (i=0; i<adj[u].size(); i++)
	{
		v = adj[u].at(i);
		if (!vis[v])
		{
			dfs(v);
			dp[u][0] += dp[v][1];
			if (dp[v][1] > dp[v][2])
			{
				dp[u][1] += dp[v][2];
				flag = 1;
			}
			else
			{
				dp[u][1] += dp[v][1];
			}
			dpu1copy += dp[v][1];
			difmin = min(difmin, dp[v][2]-dp[v][1]);
			mymin = min(dp[v][0], dp[v][1]);
			mymin = min(mymin, dp[v][2]);
			dp[u][2] += mymin;
			has_son = true;
		}
	}
	if (!flag)
	{
		dp[u][1] = dpu1copy + difmin;
	}
	dp[u][2] += 1;
	if (!has_son)
	{
		dp[u][0] = 0;
		dp[u][1] = 1;
		dp[u][2] = 1;
	}
}

int main()
{
#ifndef ONLINE_JUDGE
	ifstream fin ("0406_2384.txt");
	int n,i,u,v,root;
	fin >> n;
	if (n==1)
	{
		cout << 1;
		return 0;
	}
	for (i=0; i<n-1; i++)
	{
		fin >> u >> v;
		vis[--u]++;						// vis数组的功用一:记录节点在边集中出现的次数,找出树的根节点
		vis[--v]++;
		adj[u].push_back(v);
		adj[v].push_back(u);
	}
	fin.close();
	for (i=0; i<n; i++)					// 如果只在输入的边集中出现一次,则是根节点
	{
		if (vis[i]==1)
		{
			root = i;
			break;
		}
	}
	memset(vis,0,sizeof(vis));			// 清空vis数组,切换功用
	dfs(root);
	cout << min(dp[root][1], dp[root][2]);
	return 0;
#endif
#ifdef ONLINE_JUDGE
	int n,i,u,v,root;
	cin >> n;
	if (n==1)
	{
		cout << 1;
		return 0;
	}
	for (i=0; i<n-1; i++)
	{
		cin >> u >> v;
		vis[--u]++;						// vis数组的功用一:记录节点在边集中出现的次数,找出树的根节点
		vis[--v]++;
		adj[u].push_back(v);
		adj[v].push_back(u);
	}
	for (i=0; i<n; i++)					// 如果只在输入的边集中出现一次,则是根节点
	{
		if (vis[i]==1)
		{
			root = i;
			break;
		}
	}
	memset(vis,0,sizeof(vis));			// 清空vis数组,切换功用
	dfs(root);
	cout << min(dp[root][1], dp[root][2]);
#endif
}


猜你喜欢

转载自blog.csdn.net/da_kao_la/article/details/80765761
4.6