一. VJudge链接:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=36043
二. 题目大意:给你一颗无根树,代表电脑的网络图,要求在个别电脑上架设服务器,满足以下规则:每台没有架设服务器的电脑跟一台且只能有一台已经架设了服务器的电脑相连,求架设最少的服务器满足条件。
三. 解题思路:刚开始容易想到用dp[0][u]和dp[1][u]表示u节点没有架设服务器和架设了服务器的最优值,但是,这样写不出状态转移方程,对于dp[0][u],没有架设服务器,那么子节点可能其中有一个架设了服务器,也有可能子节点不架设服务器,父亲节点架设了服务器。因此,要引入第三维,dp[0][u],dp[2][u]表示u节点没有架设服务器,但是0表示父亲节点不是服务器,2表示父亲节点是服务器。
(1) dp[1][u]:表示节点u是服务器,则所有子节点v可以不是服务器。此时v的父亲节点是服务器。而且v也可以是服务器。dp[1][u] = sum(min(dp[1][v], dp[2][v]))+1。
(2) dp[2][u]:表示节点u不是服务器,而且父亲节点是服务器。则其所有子节点v都不能是服务器,dp[2][u] = sum(dp[0][v])。
(3) dp[0][u]:表示节点u不是服务器,而且父亲节点不是服务器。则所有子节点v中有一个是服务器。传统的求法是枚举每一个子节点,求出最小值,时间复杂度O(k^2)但是也可以利用求出来的dp[2][u],即:dp[0][u] = min(dp[2][u] + dp[1][v] - dp[0][v])。其实就是sum(dp[0][v])中枚举减去每一个v,然后加上他是服务器的情况,时间复杂度为O(k)。
注意点:初始化的时候,dp[0]全部为无穷大(可以用来最小比较),dp[1]和dp[2]为0。然后,这个算法有一个漏洞,就是对于叶子节点的父亲节点,设为u,如果u和它父亲都不架设服务器,那么只能从叶子节点选一个架设服务器,这是如果有多个叶子节点 ,那么其余叶子节点就没有服务器和它相连,因此搜到叶子节点,要让其父亲节点选择架设服务器,只要把dp[0][v]给一个足够大的值,不能太大,因为会累积,而且累积很厉害,其实N就足够了。这样父亲dp[2][u]和dp[0][u]就会很大,就会选择dp[1][u]的情况。有人会说只有一个叶子节点怎么办,这是架设在父亲节点还是最优的,可以画一条链,你会发现当然是选中间比较优,虽然我不能给出严格证明。
四. 代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <string>
using namespace std;
const int MAX_N = 10010,
INF = 0x3f3f3f3f;
vector <int> graph[MAX_N];
bool visited[MAX_N];
//dp[0][u] 非服务器,父亲非服务器
//dp[1][u] 是服务器
//dp[2][u] 非服务器,父亲是服务器
int N, dp[3][MAX_N];
void init()
{
memset(dp[0], INF, sizeof(dp[0]));
memset(dp[1], 0, sizeof(dp[1]));
memset(dp[2], 0, sizeof(dp[2]));
memset(visited, 0, sizeof(visited));
int i;
for(i = 0; i <= N; i++)
graph[i].clear();
}
void dfs(int u)
{
visited[u] = 1;
int i, v;
bool isLeaf = true;
vector <int> son;
for(i = 0; i < graph[u].size(); i++){
v = graph[u][i];
if(!visited[v]){
isLeaf = false;
dfs(v);
dp[1][u] += min(dp[1][v], dp[2][v]);
dp[2][u] += dp[0][v];
son.push_back(v);
}
}
if(isLeaf){
dp[0][u] = N;
dp[1][u] = 1;
dp[2][u] = 0;
return;
}
dp[1][u]++;
for(i = 0; i < son.size(); i++){
v = son[i];
dp[0][u] = min(dp[0][u], dp[2][u] + dp[1][v] - dp[0][v]);
}
}
int main()
{
//freopen("in.txt", "r", stdin);
int u, v, i;
while(~scanf("%d", &N)){
init();
for(i = 0; i < N-1; i++){
scanf("%d %d", &u, &v);
graph[u].push_back(v);
graph[v].push_back(u);
}
dfs(1);
printf("%d\n", min(dp[1][1], dp[0][1]));
scanf("%d", &N);
if(-1 == N)
break;
}
return 0;
}