题解:洛谷 P2996 Visiting Cows
【题目来源】
洛谷:P2996 [USACO10NOV] Visiting Cows G
【题目描述】
经过了几周的辛苦工作,Bessie 终于迎来了一个假期。
作为奶牛群中最会社交的牛,她希望去拜访 \(N(1 \le N \le 50000)\) 个朋友。这些朋友被标号为 \(1,2,\dots,N\)。这些奶牛有一个不同寻常的交通系统,里面有 \(N-1\) 条路,每条路连接了一对编号为 \(C_1\) 和 \(C_2\) 的奶牛 \((1 \le C_1 \le N, 1 \le C_2 \le N, C_1 \ne C_2)\)。这样,在每一对奶牛之间都有一条唯一的通路。
FJ 希望 Bessie 尽快的回到农场。于是,他就指示 Bessie:如果对于一条路直接相连的两个奶牛,Bessie 只能拜访其中的一个。当然,Bessie 希望她的假期越长越好,所以她想知道她可以拜访的奶牛的最大数目。
【输入】
第一行,\(1\) 个整数 \(N\)。
接下来 \(N - 1\) 行,每行 \(2\) 个整数 \(C_1,C_2\),表示有一条通路连接了编号为 \(C_1\) 和 \(C_2\) 的奶牛。
【输出】
第一行,\(1\) 个整数,代表 Bessie 最多能拜访有多少头奶牛。
【输入样例】
7
6 2
3 4
2 3
1 2
7 6
5 6
【输出样例】
4
【核心思想】
-
问题分析:给定 \(N\) 个节点的树,要求选择尽可能多的节点,使得任意两个被选节点之间没有直接相连的边(即独立集)。这是一个经典的树形 DP 问题,核心在于每个节点只有"选"或"不选"两种状态,且相邻节点不能同时选。
-
算法选择:
- 树形 DP:\(f[u][0/1]\) 表示以 \(u\) 为根的子树中,\(u\) 不选(\(0\))或选(\(1\))时的最大独立集大小
- DFS 遍历:从根节点出发,递归处理每个子树
-
关键步骤:
- 初始化:读取 \(N\) 和 \(N-1\) 条边,建立无向邻接表,找根节点(无父节点的节点)
- DFS 状态转移(节点 \(u\),父节点 \(fa\)):
- \(f[u][1] = 1\)(选 \(u\),至少包含 \(u\) 自己)
- 遍历 \(u\) 的所有邻接节点 \(v\)(跳过父节点 \(fa\)):
- 递归 \(dfs(v, u)\)
- \(f[u][0] += \max(f[v][0], f[v][1])\)(\(u\) 不选,\(v\) 可选可不选,取较大值)
- \(f[u][1] += f[v][0]\)(\(u\) 选,\(v\) 不能选)
- 输出答案:\(\max(f[root][0], f[root][1])\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N)\),每个节点访问一次,每条边处理一次
- 空间复杂度:\(O(N)\),邻接表和 DP 数组
-
树形 DP 的核心思想:
- 状态设计:每个节点只有两种状态,子问题相互独立(子树之间互不影响)
- 父子约束:选父节点则不能选子节点,不选父节点则子节点可选可不选
- 后序遍历:先递归处理所有子节点,再处理当前节点,确保子树状态已计算完毕
- 最优子结构:以 \(u\) 为根的子树的最优解仅依赖于各子树的最优解
- 适用于树的最大独立集、树的最小点覆盖、树的最小支配集类问题
【算法标签】
普及 #树形DP
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 50005; // 最大奶牛数量
int n; // n:奶牛数量(也是树的节点数)
vector<int> g[N]; // g[u]:节点u的邻接表
int f[N][2]; // f[u][0/1]:以u为根的子树中,u不选(0)/选(1)时的最大拜访数
bool st[N]; // st[i]:标记节点i是否有父节点(用于找根)
// 树形DP:dfs遍历整棵树
void dfs(int u, int fa) // u:当前节点, fa:父节点
{
f[u][1] = 1; // 如果选u,至少可以拜访u自己
for (int i=0; i<g[u].size(); i++) // 遍历u的所有子节点
{
int v = g[u][i]; // v:u的邻接节点
if (v==fa) continue; // 跳过父节点,避免回溯
dfs(v, u); // 递归处理子树
// 状态转移:u不选时,子节点v可选可不选,取较大值
f[u][0] += max(f[v][0], f[v][1]);
// 状态转移:u选时,子节点v不能选(相邻不能同时选)
f[u][1] += f[v][0];
}
}
int main()
{
cin >> n; // 读入奶牛数量
for (int i=1; i<n; i++) // 读入n-1条边
{
int c1, c2;
cin >> c1 >> c2;
g[c1].push_back(c2); // 无向图,双向建边
g[c2].push_back(c1);
st[c2] = true; // 标记c2有父节点(c1是c2的父节点之一)
}
// 找树的根节点:没有父节点的节点
int root = 1;
for (int i=1; i<=n; i++)
if (!st[i]) // 如果节点i没有父节点
{
root = i;
break;
}
dfs(root, 0); // 从根节点开始DFS
// 答案:根节点选或不选的最大值
cout << max(f[root][0], f[root][1]) << endl;
return 0;
}
【运行结果】
7
6 2
3 4
2 3
1 2
7 6
5 6
4
浙公网安备 33010602011771号