P1041 传染病控制
远古NOIP题放到现在真的容易得一批
给你一棵树,以1为根。1点有病,并且有边就会传染,一轮传染可以断边一次,求最好的断边方案下的最小感染人数。
有一个贪心的思想:当前哪一层要被感染,我们就亡羊补牢,去断这个路径上的任意一条边。
因为树是越走size越小的,去断上面已经感染的就很睿智,去断下面的最终感染的人数会更多,保护的人变少了。
很自然可以预处理出这棵树每个点的深度,然后开个桶存每个深度下的所有点。
之后就是爆搜了:
如果上一层被感染了,那么就有这么一次权利去阻止感染,可以进入下一层状态。
注意爆搜的终止条件,搜到最大深度+1是一个方面:如果这一深度的人都不会被感染,就可以结算答案了。
剪枝:因为是求最小值,所以直接无脑最优性剪枝即可。
我的错误:在下一层状态的更改用了两个循环!其实只需要两个循环一前一后即可。
代码:
/*************************************************************************
@Author: Garen
@Created Time : Wed 13 Feb 2019 04:26:31 PM CST
@File Name: P1041.cpp
@Description:
************************************************************************/
#include<bits/stdc++.h>
using std::cin;
using std::cout;
using std::endl;
#define ll long long
const int maxn = 305;
const int INF = 0x3f3f3f3f;
std::vector<int> G[maxn];
std::vector<int> help[maxn];
int dep[maxn], fa[maxn];
bool infected[maxn];
bool changed[maxn];
int n, m;
int lim;
int ans = INF;
void dfs1(int u, int f) {
dep[u] = dep[f] + 1; fa[u] = f;
for(auto v : G[u]) {
if(v == f) continue;
dfs1(v, u);
}
}
void dfs(int t, int res) {
if(res >= ans) return;
if(t == lim + 1) {
ans = std::min(ans, res);
return;
}
int cnt = 0;
for(auto u : help[t]) {
if(infected[fa[u]]) {
infected[u] = true;
changed[u] = true;
cnt++;
}
}
if(cnt == 0) {
ans = std::min(ans, res);
return;
}
for(auto u: help[t]) {
if(changed[u]) {
infected[u] = false;
dfs(t + 1, res + cnt - 1);
infected[u] = true;
}
}
for(auto u : help[t]) {
if(changed[u]) {
infected[u] = changed[u] = false;
}
}
}
int main() {
cin >> n >> m;
while(m--) {
int u, v; cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs1(1, 0);
for(int i = 1; i <= n; i++) {
lim = std::max(lim, dep[i]);
help[dep[i]].push_back(i);
}
infected[1] = true;
dfs(2, 1);
cout << ans << endl;
return 0;
}