• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
jacklee404
Never Stop!
博客园    首页    新随笔    联系   管理    订阅  订阅
NOIP-2003 传染病控制 暴力-深搜

题目

P1041 [NOIP2003 提高组] 传染病控制

思路

一开始想到的是计算每个节点的儿子数量,然后贪心一边, 但是这样是错误的,下面给出例子证明贪心法是错误的。

​ 自顶向下错误证明:

image-20230305131918860

​ 自底向上错误证明:

​ image-20230305132119595

​ 说明:由于贪心策略,我们把儿子多的节点先给挂了, 导致我们最后的节点数为200(这里上面的单个节点忽略),但是正解为100。

​ 所以这道题我们应该暴力求解,我们先记录一下每一个结点的儿子数量, 然后将每一层的边记录,然后按层枚举每一条边,这样的做法上界为\(\Theta(2^n)\) , 其中\(n\)为边的数量。

​ 这里学习一下反向边的判断, 由于加入无向图加边的时候都是成对加入的, 所以相邻的两个\(idx\)一定满足关系\(\oplus = 1\), 所以我们可以用该条件判断两个数是否为反向边。

Code

#include <iostream>
#include <vector>
#include <cstring>
#define sz(x) (int)x.size()

using i64 = long long;

const int N = 500, M = 2 * N ;

int h[N], e[M], ne[M], idx;

std::vector<int> level[N];

int cnt[N], c[M];

int n, m, ans;

void add(int a, int b) {
	ne[idx] = h[a], h[a] = idx, e[idx ++] = b;
	ne[idx] = h[b], h[b] = idx, e[idx ++] = a;
}

int dfs_level(int u, int depth, int 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];
}

void dfs_draw(int j, int color) {
	c[j] = color;

	for(int i = h[e[j]]; ~i; i = ne[i]) {
		if(i != (j ^ 1))
			dfs_draw(i, color);
	}
}

void dfs(int u, int s) {
	ans = std::min(ans, s);

	for(int i = 0; i < sz(level[u]); i ++) {
		int j = level[u][i];

		if(!c[j]) {
			dfs_draw(j, 1);
			dfs(u + 1, s - cnt[e[j]]);
			dfs_draw(j, 0);
		}
	}
}

int main() {
	std::cin >> n >> m;

	memset(h, -1, sizeof h);

	ans = n;

	for(int i = 0; i < m; i ++) {
		int a, b;
		std::cin >> a >> b;
		add(a, b);
	}

	dfs_level(1, 0, -1);

	dfs(0, n);

	std::cout << ans;
}
posted on 2023-03-05 13:42  Jack404  阅读(48)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3