这道题对于dfs搜索和图论的操作十分经典,有许多地方可以学习。
1. 如何一直往下搜索,而不搜到父节点?
2. 如何表示每条边?如何按边来枚举?
3. 如何求反向边?
4. dfs搜索后,返回时要注意恢复现场
// 传染病防治
#include <algorithm>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
const int N = 310, M = 2 * N;
int n, m;
int h[N], e[M], ne[M], idx; // 邻接表存储树
vector<int> level[N]; // level[i]存储深度为i的节点
int c[M]; // 每条边的颜色,这里用每条边的指向节点来代指这条边,因为二叉树的入度为1,一个指向节点对应一条边。
int cnt[N]; // 每课子树的大小
int ans = N; // 最小的传染病人数
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx, idx++;
}
// 预处理
int dfs_level(int u, int depth, int father) // father是为了防止向上搜索
{
cnt[u] = 1;
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == father)
continue;
cnt[u] += dfs_level(j, depth + 1, u); // 递归求子树大小
level[depth].push_back(i); // 存下层的节点下标,间接存储了每层的边
}
return cnt[u];
}
// 把边j砍掉后,后面的边标记为失效
void dfs_draw(int j, int color) // 要涂边的下标为j,
{
c[j] = color;
for (int i = h[e[j]]; ~i; i = ne[i])
{
if (i == (j ^ 1)) // @@@
continue; // 过滤掉j的反向边,
// 为什么这样可以过滤反向边?因为添加一条边是按顺序添加的,因此i->j,和j->i的idx相差为1,二进制只有末尾相反,所以异或操作,即可得到反向边对于节点的idx。
dfs_draw(i, color);
}
}
void dfs(int u, int s) // u:层数,s:当前子树节点个数
{
ans = min(ans, s);
for (int i = 0; i < level[u].size(); i++) // 枚举每条边
{
int j = level[u][i];
if (!c[j])
{
dfs_draw(j, 1);
dfs(u + 1, s - cnt[e[j]]); // 砍掉边j,进行递推
dfs_draw(j, 0);
}
}
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for (int i = 0; i < m; i++)
{
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
}
dfs_level(1, 0, -1);
dfs(0, n);
cout << ans << endl;
return 0;
}