P2491 [SDOI2011] 消防

P2491 SDOI2011 消防

算法竞赛进阶指南 P374 解法3(解法2为P1099 树网的核),7FA4.3.2.5.3, LuoguP2491 SDOI2011

  • 二分答案 mid 在树的直径上找离两端最远且距离小于 mid 的点, 判断其他点是否到这个点的距离均小于等于mid
点击查看代码

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <utility>
#include <array>
#include <vector>

using namespace std;

const int N = 300005, M = N << 1;

int n, m;
int h[N], e[M], w[M], nxt[M], idx;
int vertax[N], edge[N], tot; // 直径的端点, 边, 点的总数
int dist1[N], dist2[N], *dist; // 求直径
int sum[N]; // 直径权值的前缀和
bool vis[N]; // 是否遍历
int father[N], to_father[N]; // 父节点, 到父节点的边

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

int ans; // 最远距离
void dfs(int u) {
	vis[u] = true;
	for(int i = h[u]; i; i = nxt[i]) {
		int v = e[i];
		if(vis[v]) continue;
		father[v] = u;
		to_father[v] = i;
		dist[v] = dist[u] + w[i];
		ans = max(ans, dist[v]);
		dfs(v);
	}
}

int furthest(int u, int *d) {
	dist = d, dist[u] = 0, father[u] = to_father[u] = -1;
	memset(vis, false, sizeof(vis)), dfs(u);
	return max_element(dist + 1, dist + n + 1) - dist;
}

bool check(int mid) {
	int p = 1, q = tot;
	while(p + 1 <= tot && sum[p + 1] <= mid) p ++; // 左边的最右
	while(q - 1 >= 1 && sum[tot] - sum[q - 1] <= mid) q --; // 右边的最左
	if(p > q) return true;
	if(sum[q] - sum[p] > m) return false;
	memset(dist, 0, sizeof(dist1));
	memset(vis, false, sizeof(vis));
	for(int i = 1; i <= tot; i ++) vis[vertax[i]] = true;
	// O(n) 判断: 这条链将这个树分为若干个连通块
	for(int i = p; i <= q; i ++) {
		int &v = vertax[i];
		ans = 0, dfs(v);
		if(ans > mid) return false;
	}
	return true;
}

int main() {
	scanf("%d%d", &n, &m);
	int sss = 0;
	for(int i = 1, a, b, c; i < n; i ++) {
		scanf("%d%d%d", &a, &b, &c);
		add(a, b, c), add(b, a, c);
		sss += c;
	}
	int x = furthest(1, dist1), y = furthest(x, dist1);
	x = furthest(y, dist2); // x, y 为端点
	int posx = x;
	tot = 1;
	while(posx != y) vertax[tot] = posx, edge[++ tot] = to_father[posx], posx = father[posx];
	vertax[tot] = y; // 以上为找直径
	for(int i = 1; i <= tot; i ++) {
		sum[i] = sum[i - 1] + w[edge[i]];
	}
	int l = 0, r = sss;
	while(l < r) {
		int mid = ((long long)l + r) >> 1;
		if(check(mid)) r = mid;
		else l = mid + 1;
	}
	printf("%d\n", l);
	return 0;
}
posted @ 2022-09-28 10:21  azzc  阅读(24)  评论(0编辑  收藏  举报