[AGC002D] Stamp Rally

\(\text{Solution}\)

很容易想到二分,然后 \(DFS\) 走所有能走的边,看是否大于等于 \(z\)
这样就是 \(O(nm \log m)\)
改进措施,思维不够,暴力来凑
先改变下 \(check\)
\(\le mid\) 的边加进图中,并查集维护连通性和 \(size\)
直接判 \(x,y\)\(size\) 即可
可二分,又多次,那就整体二分,可撤销并查集处理下即可
\(O(m\log m\log n)\)

其实可以更简单
仍旧二分,考虑一个点到另一个点会走的边,显然是最小生成树上的边
考虑 \(\text{Kruskal}\) 重构树的性质,不难想到倍增来 \(check\) 的做法
\(O(m\log m\log n)\)

\(\text{Code}\)

#include <cstdio>
#include <iostream>
#define IN inline
#define RE register
using namespace std;

const int N = 2e5 + 5, LG = 19;
int n, m, q, f[N][LG], fa[N], val[N], h[N], tot, cnt[N];
struct edge{int to, nxt;}e[N];
IN void add(int x, int y){e[++tot] = edge{y, h[x]}, h[x] = tot;}

IN void read(int &x)
{
	x = 0; char ch = getchar();
	for(; !isdigit(ch); ch = getchar());
	for(; isdigit(ch); x = (x<<3)+(x<<1)+(ch^48), ch = getchar());
}
int find(int x){return fa[x] == x ? x : (fa[x] = find(fa[x]));}
void Dfs(int x)
{
	if (x <= n) cnt[x] = 1;
	for(RE int i = 1; i < LG; i++)
		if (f[x][i - 1]) f[x][i] = f[f[x][i - 1]][i - 1]; else break;
	for(RE int i = h[x]; i; i = e[i].nxt)
		f[e[i].to][0] = x, Dfs(e[i].to), cnt[x] += cnt[e[i].to];
}
IN void Kruskal()
{
	int SIZE = n;
	for(RE int i = 1; i < n * 2; i++) fa[i] = i;
	for(RE int i = 1, x, y, u, v; i <= m; i++)
		read(x), read(y), u = find(x), v = find(y),
		(u ^ v) && (val[++SIZE] = i, add(SIZE, u), add(SIZE, v), fa[u] = fa[v] = SIZE);
	Dfs(SIZE);
}
IN int check(int mid, int x, int y, int z)
{
	for(RE int i = LG - 1; i >= 0; i--)
		f[x][i] && (val[f[x][i]] <= mid) && (x = f[x][i]),
		f[y][i] && (val[f[y][i]] <= mid) && (y = f[y][i]);
	if (x == y)	return (cnt[x] >= z);
	return (cnt[x] + cnt[y] >= z);
}

int main()
{
	read(n), read(m), Kruskal(), read(q);
	for(RE int x, y, z, l, r, mid, ans; q; --q)
	{
		read(x), read(y), read(z), l = 1, r = m;
		for(mid = l + r >> 1; l <= r; mid = l + r >> 1)
			if (check(mid, x, y, z)) ans = mid, r = mid - 1; else l = mid + 1;
		printf("%d\n", ans);
	}
}
posted @ 2021-12-17 13:35  leiyuanze  阅读(28)  评论(0编辑  收藏  举报