当前位置: 首页 > news >正文

NOIP2012提高组.疫情控制

题目

357. 疫情控制
在这里插入图片描述

算法标签: 树, 二分, 倍增, l c a lca lca, 贪心

思路

  1. 除了首都, 军队都不应该向下移动, 因为向下移动会使管辖的区域变小, 且军队往上移动越多越好, 也就是管辖的区域变大
  2. 考虑每个军队最多往上移动多少, 二分

在这里插入图片描述
检查 t t t时间内军队是否能移动到既定位置, 进行二分

假设二分出来最优的时间是 t t t, 就可计算每个军队在 t t t时间内最多向上走到哪个位置, 然后判断这些位置是否保证每个根节点到叶子结点上路径有军队, 可以倍增进行优化

  1. 对于无法走到根节点的军队, 使其向上能走到哪就走到哪
  2. 然后统计有哪些根节点的子树未被完全覆盖, 记为集合 A A A
  3. 对于能走到根节点的军队, 首先走到根节点的一个儿子上, 然后军队有两种选择原地驻扎或者先走到首都再走到其他城市, 记为集合 B B B

那么问题就转化为, 哪些 B B B集合中军队必须原地驻扎
在这里插入图片描述
假设橙色子树节点未完全覆盖, 并且时间不足以从当前位置走到根节点再回到当前位置, 那么该位置军队是不能移动的

考虑 A A A集合中某个点(也就是未被覆盖的根节点的某个儿子), 选出到达该位置剩余时间最少的军队, 如果该军队走到首都再走回来时间不够, 那么必然存在一组最优解使得该军队在该节点位置原地驻扎

去除已经原地驻扎的军队覆盖的城市 A ′ A' A, 剩余军队 B ′ B' B, 剩余军队就是从当前位置出发走到根节点再走到其它城市, 因此可以统计一下 B ′ B' B军队走到根后剩余的时间, 然后计算每个 A ′ A' A中城市到根节点的距离, 将上述两个时间和距离排序, 也就是一个贪心问题, 一定存在一组最优解使得是保序覆盖

在这里插入图片描述
可以将交叉方案调整为保序覆盖, 使得不交叉

时间复杂度 O ( n log ⁡ 2 n ) O(n\log ^ 2n) O(nlog2n)

代码

#pragma GCC optimize(2)

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;
const int N = 50010, K = 16;
const LL INF = 1e18;

int n, m;
vector<PII> head[N];
int fa[N][K], pos[N], tmp[N];
LL dist[N];
bool has_army[N], vis[N];
struct Node {
	int ver;
	LL rest;
	bool is_del;
} nodes[N];

void add(int u, int v, int w) {
	head[u].push_back({v, w});
}

void dfs_fa(int u, int pre, LL d) {
	dist[u] = d;
	for (auto [v, w]: head[u]) {
		if (v == pre) continue;
		fa[v][0] = u;
		for (int k = 1; k < K; ++k) fa[v][k] = fa[fa[v][k - 1]][k - 1];
		dfs_fa(v, u, d + w);
	}
}

bool is_leaf(int u) {
	return u != 1 && head[u].size() == 1;
}

void dfs_g(int u, int pre, bool has) {
	has |= has_army[u];
	if (is_leaf(u)) vis[u] = has;
	else {
		vis[u] = true;
		for (auto [v, w]: head[u]) {
			if (v == pre) continue;
			dfs_g(v, u, has);
			if (!vis[v]) vis[u] = false;
		}
	}
}

bool check(LL mid) {
	memset(has_army, 0, sizeof has_army);

	int cnt = 0;
	for (int i = 0; i < m; ++i) {
		int u = pos[i];
		for (int k = K - 1; k >= 0; --k) {
			if (fa[u][k] > 1 && dist[pos[i]] - dist[fa[u][k]] <= mid) u = fa[u][k];
		}
		if (dist[pos[i]] <= mid) {
			nodes[cnt++] = {u, mid - (dist[pos[i]] - dist[u]), false};
		}
		// 能向上跳的最大距离
		else has_army[u] = true;
	}

	memset(vis, 0, sizeof vis);
	dfs_g(1, -1, false);

	// 存储A集合中剩余时间最少的军队编号
	int army_idx[N];
	memset(army_idx, -1, sizeof army_idx);

	// 求每个城市剩余时间最少的军队
	for (int i = 0; i < cnt; ++i) {
		int ver = nodes[i].ver;
		if (army_idx[ver] == -1 || nodes[army_idx[ver]].rest > nodes[i].rest) army_idx[ver] = i;
	}

	// 枚举集合A中的城市
	for (auto [v, w]: head[1]) {
		if (!vis[v]) {
			int k = army_idx[v];
			// 不能走到根节点再走回来
			if (k != -1 && nodes[k].rest < dist[nodes[k].ver] * 2) {
				nodes[k].is_del = true;
				vis[v] = true;
			}
		}
	}

	// 存储未被删除的军队
	int ncnt = 0;
	for (int i = 0; i < cnt; ++i) {
		if (!nodes[i].is_del) nodes[ncnt++] = nodes[i];
	}

	int gcnt = 0;
	for (auto [v, w]: head[1]) {
		if (!vis[v]) tmp[gcnt++] = v;
	}

	sort(nodes, nodes + ncnt, [](const Node &a, const Node &b) {
		return a.rest - dist[a.ver] < b.rest - dist[b.ver];
	});
	sort(tmp, tmp + gcnt, [](int a, int b) {
		return dist[a] < dist[b];
	});

	// 贪心最大匹配
	int k = 0;
	for (int i = 0; i < ncnt && k < gcnt; ++i) {
		if (nodes[i].rest - dist[nodes[i].ver] >= dist[tmp[k]]) k++;
	}
	return k == gcnt;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	cin >> n;
	for (int i = 0; i < n - 1; ++i) {
		int u, v, w;
		cin >> u >> v >> w;
		add(u, v, w), add(v, u, w);
	}

	dfs_fa(1, -1, 0);

	cin >> m;
	for (int i = 0; i < m; ++i) cin >> pos[i];

	LL l = 0, r = INF;
	while (l < r) {
		LL mid = l + r >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}

	if (r == INF) r = -1;
	cout << r << "\n";
	return 0;
}

相关文章:

  • 【Python 开源】你的 Windows 关机助手——PyQt5 版定时关机工具
  • 二分查找3:69. x 的平方根
  • 使用Docker安装Gogs
  • MySQL 半同步复制,给数据找靠谱 “分身”
  • MCP协议下人工智能康复理疗智械融合编程方向分析
  • 【转载翻译】使用Open3D和Python进行点云处理
  • Matlab 三维时频图
  • 使用WasmEdge将InternLM集成到Obsidian,打造本地智能笔记助手
  • LibreOffice Writer使用01去除单词拼写判断的红色下划线
  • sql server统计小时数据中每日最大风速及风速对应的风向
  • 华为机试 - 跳房子I
  • 让 Python 脚本在后台持续运行:架构级解决方案与工业级实践指南
  • (51单片机)点阵屏LED显示图片(点阵屏LED教程)(74Hc595教程)
  • 简单易懂的解读50ETF分红对期权造成的影响有哪些?
  • Python 深度学习实战 第3章 Keras和TensorFlowKeras 训练和评估模型实例
  • LeetCode hot 100—子集
  • TCP专题
  • C#高级语法:Linq中使用SelectMany解决列表子项提取、双重for循环的问题
  • 06软件测试需求分析案例-添加用户
  • NLP高频面试题(四十)——什么是 BitFit?
  • 网站建设大数据/网站整体优化
  • 做移动网站建设/免费制作网站的软件
  • 网站开发任务单百度文库/成人短期技能培训学校
  • 漳州网站建设 林/能打开各种网站的浏览器下载
  • 快速学习网站制作/广州:推动优化防控措施落地
  • 南京谁做免费网站/新闻20条摘抄大全