「Codeforces516D」Drazil and Morning Exercise

Description

给出一棵 n n n 个点的树,给出 n − 1 n-1 n1 x x x y y y 之间的边。

每条边有一个长度 v v v,询问 q q q次,每次询问给出一个 L L L,请选择一个符合要求的最大联通块,要求该联通块中所有点到树上离它最远的点的距离的最大值与最小值之差小于等于 L L L,输出联通块的最大大小。

n ≤ 100000 , q ≤ 50 n \leq 100000, q \leq 50 n100000,q50

Solution

q ≤ 50 q \leq 50 q50,显然没啥用。

d ( i ) d(i) d(i)表示 i i i到树上离它最远的点的距离。显然这个直接预处理。

然后枚举从大到小枚举最小的 d d d t t t,维护满足 d ≤ t + l i m d \leq t+lim dt+lim点形成的联通块大小,lct即可

上面那个方法太难写了(而且不太能过),考虑挖掘一些别的性质,可以发现树上离一个点最远的点只会是直径的两端点,那么如果以直径的中点为根(可以发现这是 d d d最小的点),可以发现每个点子树里的 d d d值都不小于子树根的 d d d值。

有了这个性质就很好做了,从大到小枚举最小的 d d d x x x,对于 d d d值超过 d + l i m d+lim d+lim的点直接删掉,因为它的子树肯定已经被删掉了,所以不会影响连通性。所以可以用并查集维护。时间复杂度 O ( n + n q α ( n ) ) O(n+nq\alpha(n)) O(n+nqα(n))

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

inline int gi()
{
	char c = getchar();
	while(c < '0' || c > '9') c = getchar();
	int sum = 0;
	while('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
	return sum;
}

inline ll gl()
{
	char c = getchar();
	while(c < '0' || c > '9') c = getchar();
	ll sum = 0;
	while('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
	return sum;
}

const int maxn = 100005;

int n, ans;

struct edge
{
	int to, next, w;
} e[maxn * 2];
int h[maxn], tot, rt, fa[maxn], p[maxn], siz[maxn], vis[maxn];
ll dis[maxn], d[maxn];

inline void add(int w, int u, int v)
{
	e[++tot] = (edge) {v, h[u], w}; h[u] = tot;
	e[++tot] = (edge) {u, h[v], w}; h[v] = tot;
}												

void dfs(int u, int fa)
{
	d[u] = max(d[u], dis[u]);
	if (dis[u] > dis[rt]) rt = u;
	for (int i = h[u], v; v = e[i].to, i; i = e[i].next)
		if (v != fa) dis[v] = dis[u] + e[i].w, dfs(v, u);
}

int find(int x)
{
	if (fa[x] == x) return x;
	return fa[x] = find(fa[x]);
}

void merge(int x, int y)
{
	x = find(x); y = find(y);
	if (x == y) return ;
	if (siz[y] > siz[x]) swap(x, y);
	siz[x] += siz[y]; ans = max(ans, siz[x]); fa[y] = x;
}

int main()
{
	n = gi(); ++n; --n;
	for (int i = 1; i < n; ++i) add(gi(), gi(), gi());
	
	dfs(1, 0);
	int tmp = rt; dis[rt] = 0; rt = 0;
	dfs(tmp, 0);
	dis[rt] = 0; dfs(rt, 0);

	for (int i = 1; i <= n; ++i) p[i] = i;
	sort(p + 1, p + n + 1, [](const int &a, const int &b) {return d[a] < d[b];});
	int q = gi();
	ll lim;
	while (q--) {
		lim = gl();
		for (int i = 1; i <= n; ++i) fa[i] = i, siz[i] = 1, vis[i] = 0;

		ans = 1;
		for (int k = n, j = n; k >= 1; --k) {
			while (d[p[j]] > d[p[k]] + lim) {
				--siz[find(p[j])];
				--j;
			}
			vis[p[k]] = 1;
			for (int i = h[p[k]], v; v = e[i].to, i; i = e[i].next)
				if (vis[v]) merge(p[k], v);
		}
		printf("%d\n", ans);
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值