题目分析
题意:给出一棵树,需要在这棵树的结点上安装通信装备,每个通信装备可以覆盖当前结点和所有和这个结点直接相连的结点,问安装最少多少个通信装备可以覆盖整棵树。
这个题目可以说是树形DP的题目,也可以是最小支配集的题目,我们用下列变量记录数据
1)dp [ u ] [ 0 ] 表示在结点 u 不安装设备,但父结点安装设备的情况下,记录以 u 为根结点的树(父节点不算这个树内)被完全覆盖的情况下,安装最少的设备数。
2)dp [ u ] [ 1 ] 表示在结点 u 安装设备的情况下,使用最少的设备数。
3)dp [ u ] [ 2 ] 表示在结点 u 不安装设备,但是 u 的子结点中存在安装了设备的结点的情况下,使用最少的设备数。
【1】那么求dp [ u ] [ 0 ] 时,由于 u 的父节点已经覆盖了 u ,那么 u 的子结点安装或不安装都可以,因此
dp [ u ] [ 0] += min( dp [ v ] [ 1 ] , dp [ v ] [ 2 ] )
【2】而求 dp [ u ] [ 1 ] 的时候,由于 u 已经安装了设备,所以其子结点无论怎么样都可以,因此
dp [ u ] [ 0] += min( dp [ v ] [ 0 ] , dp [ v ] [ 1 ],dp [ v ] [ 2 ] )
【3】在求 dp [ u ] [ 2 ] 的时候,由于 u 不选,所以我们需要在 dp [ v ] [ 1 ] 和 dp [ v ] [ 2 ] 中选取最小的,但是dp [ u ] [ 2 ] 需要保证其子结点可以覆盖u,所以至少选选择一个 dp [ v ] [ 1 ] 用来覆盖 u , 而为了让安装的设备数最少,选取的dp [ v ] [ 1 ] 和 dp [ v ] [ 2 ] 的差值应当最小,因此
dp [ u ] [ 2 ] = min( dp [ v ] [ 1 ] , dp [ v ] [ 2 ] ) 【必须选取一个dp [ v ] [ 1 ] 和 dp [ v ] [ 2 ] 差值最小的dp [ v ] [ 1 ] 】
代码区
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<string>
using namespace std;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int Max = 1e5 + 10;
struct Edge
{
int to;
int next;
}edge[Max];
int head[Max], tot;
int dp[Max][3];
int n;
void add(int u, int v)
{
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
}
void dfs(int u, int pre)
{
dp[u][0] = 0;
dp[u][1] = 1;
dp[u][2] = 0;
int gap = inf; //记录dp[u][1]和dp[u][2]的最小差距
bool ok = false;
for (int i = head[u];i != -1; i = edge[i].next)
{
int v = edge[i].to;
if (v == pre) continue;
dfs(v, u);
dp[u][0] += min(dp[v][1], dp[v][2]);
dp[u][1] += min(dp[v][0], min(dp[v][1], dp[v][2]));
if (dp[v][2] < dp[v][1]) //选取v中的部分结点而不选v,比选v人数更少
{
dp[u][2] += dp[v][2];
gap = min(gap, dp[v][1] - dp[v][2]); //记录选和不选之间的最小差距
}
else
{
dp[u][2] += dp[v][1];
ok = true; //选取了v,那么u就满足了
}
}
if (!ok) //没有选取一个子结点,那么就必须选择一个
{
dp[u][2] += gap;
}
}
int main()
{
std::ios::sync_with_stdio(false);
while (cin >> n)
{
memset(head, -1, sizeof(head));
tot = 0;
for (int i = 1;i < n; i++)
{
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
}
dfs(1, 0);
cout << min( dp[1][1], dp[1][2]) << endl; //根结点没有父结点
}
return 0;
}