AGC002

D - Stamp Rally

  • 题意:给定\(n<=100 000\)个点的无向图,有\(m<=100 000\)条边,输入\(m\)条边,第\(i\)条边,边权是\(i\),有\(q <= 100 000\)个询问,每个询问给出两个点\(x\)\(y\),和数量\(z\),求从\(x\)点和\(y\)点出发,任意恰好走\(z\)条边,求经过的最大边权最小是多少。
  • 题解:用了黑科技克鲁斯卡尔重构树,好像用可持久化并查集和整体二分也能过,因为感觉克鲁斯卡尔重构树较为容易掌握,就先学了这个。克鲁斯卡尔重构树就是在克鲁斯卡尔最小生成树上,按边权从小到大的顺序,新增\(n-1\)个点,将每条边的两个端点的父亲节点连向新增的点,新增的点权即为边权。
  • 代码:
#include <iostream>
#include <vector>
using namespace std;
const int N = 1e6 + 9;
const int inf = 0x3f3f3f3f;
vector<int>G[N];
int f[N];
int Find(int x) {return f[x] == x?x:f[x] = Find(f[x]);}
int V[N];
int sz[N];
int fa[N][20];
void dfs(int u, int father) {
	fa[u][0] = father;
	for (int i = 1; i <= 17; i++) {
		fa[u][i] = fa[fa[u][i-1]][i-1];
	}
	for (auto v : G[u]) {
		if (father == v)continue;
		dfs(v, u);
		sz[u] += sz[v];
	}
	if (sz[u] == 0)sz[u]++;
	return ;
}
bool check(int x, int y, int mid, int cnt) {
	for (int i = 17; i >= 0; i--) {
		if (V[fa[x][i]] <= mid)x = fa[x][i];
		if (V[fa[y][i]] <= mid)y = fa[y][i];
	}
	int t = 0;
	if (x == y) t = sz[x];
	else t = sz[x] + sz[y];
	if (t < cnt)return 1;
	else return 0;
}
int main() {
	int n, m;
	ios::sync_with_stdio(0);
	cin >> n >> m;
	for (int i = 1; i <= n + n; i++)f[i] = i;
	int cnt = 0;
	V[0] = inf;
	for (int i = 1;  i <= m; i++) {
		int u, v, w;
		cin >> u >> v;
		int fx = Find(u);
		int fy = Find(v);
		if (fx != fy && cnt <= n-1){
			cnt++;
			int node = n + cnt;
			V[node] = i;
			G[node].push_back(fx);
			G[node].push_back(fy);
			f[fx] = node;
			f[fy] = node;
		}
	}
	dfs(n + cnt, 0);
	int q;
	cin >> q;
	while (q--) {
		int u, v, z;
		cin >> u >> v >> z;
		int l = 0, r = n* 2;
		int ans;
		while (l < r) {
			int mid = l + r >> 1;			
			if (check(u, v, mid, z)) {
				l = mid + 1;
			} else {
				ans = r;r = mid;
			}
		}
		cout << l << endl;
	}
}
posted @ 2021-01-28 12:19  u_yan  阅读(56)  评论(0编辑  收藏  举报