\(\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);
}
}