P2899 [USACO08JAN]手机网络Cell Phone Network
知识点: 树形DP
原题面
题目要求:
给定一棵无根树,
花费 1价值, 覆盖一个点, 并使该点直接相邻的其他点 也被覆盖
求 将所有点全部覆盖的 最小价值
分析题意:
对于 树上的一个结点, 若要被覆盖,
可被其父, 其子覆盖, 或自己覆盖自己
覆盖一个结点, 只能影响到与其直接相连的结点,
与其距离\(1\)以上的结点 不能被影响, 显然可以DP
对于一棵子树, 若其中所有节点已被覆盖.
现考虑子树根节点的父亲的 覆盖情况, 对此子树的影响
- 若子树的根节点 自覆盖 :
- 可不花费额外代价的情况下, 将子树根节点的父亲覆盖,
- 也可花费额外1代价, 将子树的根节点的父亲覆盖
从而覆盖子树的根结点, 并影响其二级子节点
- 若子树的根节点 未被自己覆盖 :
即子树的根结点 被其子覆盖
则 可花费1代价, 将子树的根节点父亲覆盖
从而覆盖子树的根结点, 并影响其二级子节点
由上, 通过一个节点被覆盖的来源, 可以设计三种状态:
设\(f[i][0/1/2]\)表示:
当 第\(i\)个节点为根的子树全部被覆盖,
第\(i\)个结点被 其父亲/自己/儿子 覆盖时,
第\(i\)个节点为根的子树内 自己覆盖自己的点的个数
对于以结点\(i\) 为根结点的子树,
其上述三个状态的值 可以通过下述方法获得:
- \(f[i][0]\) , 即为 \(sum (min(f[j][1], f[j][2])) (j \in son[i])\)
- \(f[i][1]\), 将结点i进行自覆盖后, 只能影响到 其直接子节点
其直接子节点 的状态可以为以上三种任一
则有: \(f[i][1] = 1 + sum (min(f[j][0], f[j][1], f[j][2])) (j \in son[i])\) - \(f[i][2]\), 根节点i被其子覆盖
则其子中 必然有至少一进行了自覆盖.
其它可进行自覆盖, 也可进行 子覆盖 (但不可进行父覆盖)-
先抛开 必须有一进行自覆盖这一限制条件,
则必然选择 子覆盖和自覆盖 中代价更小的
即: 若满足\(f[j][2] - f[j][1] > 0\), 选择 自覆盖, 否则选择子覆盖 -
则必须进行自覆盖的子结点, 选择\(f[j][2] - f[j][1]\)最大的 最优
对于其他的子节点, 当 \(f[j][2] - f[j][1] > 0\) 时, 选择自覆盖, 否则选择子覆盖 -
可以设计一比较巧妙的算法实现上述过程:
先使 \(f[i][2] = sum(f[j][1]) (j \in son[i])\), 同时记录所有\(f[j][2] - f[j][1]\)
之后 对记录的\(f[j][2] - f[j][1]\) 进行升序排序
然后从头到尾进行选择, 若\(f[j][2] - f[j][1] < 0\), 说明 \(f[j][1] > f[j][2]\)
此时使 \(f[i][2] += f[j][2] - f[j][1]\), 则可消除之前选择f[j][1]的影响当选择了 \(son - 1\) 个或者 \(f[j][2] - f[j][1] >=0\) 时结束选择
-
#include <cstdio>
#include <ctype.h>
#include <vector>
#include <algorithm>
#define min std :: min
const int MARX = 1e4 + 10;
//=============================================================
struct Edge
{
int u, v, ne;
} e[MARX << 1];
int N, num, head[MARX];
int f[MARX][3];//0父亲, 1自己, 2儿子
//=============================================================
inline int read()
{
int s = 1, w = 0; char ch = getchar();
for(; !isdigit(ch); ch = getchar()) if(ch == '-') s = -1;
for(; isdigit(ch); ch = getchar()) w = (w << 1) + (w << 3) + (ch ^ '0');
return s * w;
}
void addedge(int u, int v)
{
e[++ num].u = u, e[num].v = v;
e[num].ne = head[u], head[u] = num;
}
void dfs(int u, int fa)
{
f[u][1] = 1;//赋初始值
std :: vector <int> son;
for(int i = head[u]; i; i = e[i].ne)
if(e[i].v != fa)
{
dfs(e[i].v, u);//优先更新子树
f[u][0] += min(f[e[i].v][1], f[e[i].v][2]);//u被父覆盖
f[u][1] += min(f[e[i].v][0], min(f[e[i].v][1], f[e[i].v][2]));//u被自覆盖
f[u][2] += f[e[i].v][1], son.push_back(f[e[i].v][2] - f[e[i].v][1]);//u子覆盖, 记录所有 f[e[i].v][2] - f[e[i].v][1]
}
std :: sort(son.begin(), son.end());//排序`
if(! son.size()) f[u][2] = MARX;//无子
for(int i = 0, size = son.size(); i < size - 1; i ++)
if(son[i] < 0) f[u][2] += son[i];//选择替换, 消除影响
else break;
}
//=============================================================
signed main()
{
N = read();
for(int i = 1; i < N; i ++)
{
int u = read(), v = read();
addedge(u, v), addedge(v, u);
}
dfs(1, 0);
printf("%d", min(f[1][1], f[1][2]));
}