【Luogu P1084】[NOIP2012 提高组] 疫情控制

链接:

洛谷

题目大意:

\(H\) 国有 \(n\) 个城市,这 \(n\) 个城市用 \(n-1\) 条双向道路相互连通构成一棵树,\(1\) 号城市是首都,也是树中的根节点。

\(H\) 国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。

现在,在 \(H\) 国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。

请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。

正文:

这是一道思路简单,实现不易的恶心题。

很容易发现两个性质:

  • 军队越往上,贡献越大。
  • 时间越长,贡献越大。

所以我们可以二分时间,每次就先让军队只往上走,这个用倍增实现,对于根的某个子树没有军队的,就把其他有多余军队的调过来。总时间复杂度 \(\mathcal{O}(n\log^2 n)\)

代码:

const int N = 5e4 + 10;

inline ll Read()
{
	ll x = 0, f = 1;
	char c = getchar();
	while (c != '-' && (c < '0' || c > '9')) c = getchar();
	if (c == '-') f = -f, c = getchar();
	while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
	return x * f;
}

int n, m;
int a[N];
int head[N], tot;
struct edge
{
	int to, nxt; ll val;
}e[N << 1];
void add(int u, int v, ll w)
{
	e[++tot] = (edge) {v, head[u], w}, head[u] = tot; 
}

int f[N][30], len[N][30], dep[N];
queue <int> q;
void bfs()
{
	q.push(1);
	dep[1] = 1;
	while (!q.empty())
	{
		int u = q.front(); q.pop();
		for (int i = head[u]; i; i = e[i].nxt)
		{
			int v = e[i].to;
			if (v == f[u][0]) continue;
			dep[v] = dep[u] + 1;
			f[v][0] = u;
			len[v][0] = e[i].val;
			for (int j = 1; j <= 25; j++)
				f[v][j] = f[f[v][j - 1]][j - 1],
				len[v][j] = len[v][j - 1] + len[f[v][j - 1]][j - 1];
			q.push(v);
		}
	}
}

struct node
{
	ll val; int id;
	bool operator < (const node &a) const
	{ return val < a.val;}
}c[N];
ll b[N], T[N], nd[N];
bool hav[N], need[N];
bool dfs(int u)
{
	bool flag = 1;
	if (hav[u]) return 0;
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (v == f[u][0]) continue;
		flag = 0;
		if (dfs(v)) return 1;
	}
	return flag;
}

bool check(ll t)
{
	memset (need, 0, sizeof need);
	memset (hav, 0, sizeof hav);
	memset (nd, 0, sizeof nd);
	memset (T, 0, sizeof T);
	memset (c, 0, sizeof c);
	for (int i = 1; i <= m; i++) b[i] = a[i];
	int k = 0, l = 0, g = 0;
	for (int i = 1; i <= m; i++)
	{
		ll tmp = t;
		for (int j = 25; ~j; j--)
			if (tmp >= len[b[i]][j] && f[b[i]][j] > 1) 
				tmp -= len[b[i]][j], b[i] = f[b[i]][j];
		if (f[b[i]][0] == 1 && tmp >= len[b[i]][0]) 
			c[++k] = (node){tmp - len[b[i]][0], b[i]};
		else hav[b[i]] = 1;
	}
	for (int i = head[1]; i; i = e[i].nxt)
		if (dfs(e[i].to)) 
			need[e[i].to] = 1;
	sort (c + 1, c + k + 1);
	for (int i = 1; i <= k; i++)
		if (need[c[i].id] && c[i].val < len[c[i].id][0])
			need[c[i].id] = 0;
		else T[++l] = c[i].val;
	for (int i = head[1]; i; i = e[i].nxt)
		if (need[e[i].to]) nd[++g] = len[e[i].to][0];
	if (l < g) return 0;
	sort (T + 1, T + 1 + l), sort (nd + 1, nd + 1 + g);
	int i = 1, j = 1;
	for (; i <= l && j <= g; )
		if (T[i] >= nd[j]) i++, j++;
		else i++;
	return j > g;
}

int main()
{
//	freopen(".in", "r", stdin);
//	freopen(".out", "w", stdout);
	n = Read();
	int tmp = 0;
	ll l = 0, r = 0;
	for (int i = 1; i < n; i++)
	{
		int u = Read(), v = Read(); ll w = Read();
		r += w;
		add (u, v, w), add(v, u, w);
		if (u == 1 || v == 1) tmp++;
	}
	bfs();
	m = Read();
	if (tmp > m) {puts("-1"); return 0;} 
	for (int i = 1; i <= m; i++) a[i] = Read();
	while (l < r)
	{
		ll mid = l + r >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	printf ("%lld\n", l);
	return 0;
}
posted @ 2021-10-21 10:12  Jayun  阅读(38)  评论(0编辑  收藏  举报