P1041 [NOIP 2003 提高组] 传染病控制
题目含义
有一棵树,当前根节点已经被传染,传染将顺着边每次前进一层(即传染至下一层的节点中),可以每次在当前层切断一条边,以保住切断后的子树部分,求如何切能够使得传染的人最少,即最后留在树上的节点数最少。
跑一下样例看看我的理解对不对

最后剩下3个点。
看起来理解没啥问题
分析过程
那如何做呢?即如何选择每次删哪条边?
我们发现n=300,搜索是ok的,接下来直接搜索,每一种切割方案都被搜索到,在这个过程中迭代出最优的方案就行。
能不能剪枝呢?
在这一过程中,选择最优化剪枝,即留下的结点数已经被ans大了,就不需要再继续算了,它肯定不会是答案了。
规划一下如何写?
从根结点开始搜,搜到第i层,在这一层中枚举没有被删除的边,构建答案。
(这里提示我们要记录层,记录哪些边被删了,哪些边没被删)
搜索到最后一层,更新答案,即此时留下几个点,可以用n-删除的点的数量
(要预处理出每个点的层号,以每个结点为根的子树的大小,此外,每一层上有哪些边是也需要处理的。)
void dfs(int u, int 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);//删它和它子树的边// 向下一层搜索,还有结点s-size[e[j]]个 dfs(u + 1, s - cnt[e[j]]);dfs_draw(j, 0);//恢复现场 }}
}
要删除的边,同时通往它下面所有的子结点的边都会被删掉,可以对这这边打个标记。
/x/删了该边标为1,没删为0
void cut(int j, int color)
{c[j] = color;//给这条边标记颜色//这条边指向结点的链表 for (int i = h[e[j]]; ~i; i = ne[i])if (i != (j ^ 1)) // i不是j的反向边cut(i, color);
}
综上,在实现时,先通过dfs预处理出每个点的深度,它的父亲,以它为根的子树的节点个数。
//删了该边标为1,没删为0
void cut(int j, int color)
{c[j] = color;//这条边指向结点的链表 for (int i = h[e[j]]; ~i; i = ne[i])if (i != (j ^ 1)) // i不是j的反向边cut(i, color);
}
代码实现
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>using namespace std;const int N = 310, M = N * 2;int n, m;
int h[N], e[M], ne[M], idx;
vector<int> level[N];
int c[M], siz[N];
int ans = N;void add(int a, int b)
{e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}int dfs1(int u, int depth, int father)
{siz[u] = 1;for (int i = h[u]; ~i; i = ne[i]){int j = e[i];if (j == father) continue;siz[u] += dfs1(j, depth + 1, u);//i号边是第 depth 层的边(边的索引)level[depth].push_back(i);}return siz[u];
}
//删了该边标为1,没删为0
void cut(int j, int color)
{c[j] = color;//这条边指向结点的链表 for (int i = h[e[j]]; ~i; i = ne[i])if (i != (j ^ 1)) // i不是j的反向边cut(i, color);
}void dfs(int u, int s)
{ans = min(ans, s);//枚举每一层的边 for (int i = 0; i < level[u].size(); i ++ ){int j = level[u][i];//找到一条没有被删的边 if (!c[j]){cut(j, 1);//删它和它子树的边// 向下一层搜索,还有结点s-size[e[j]]个 dfs(u + 1, s - siz[e[j]]);cut(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);}dfs1(1, 0, -1);//从0层开始搜,现在还有n个 dfs(0, n);cout << ans << endl;return 0;
}
我想说的是……
- 涉及到层、点、边的问题,代码实现有点绕,要注意
