[CF1706E]Qpwoeirut and Vertices 题解

upd:这篇文章中 Kruskal 重构树的写法是我在没学过的时候瞎胡的,并不标准,感兴趣可以来这篇文章学习一下。

传送门QAQ

Preface

这场 Div.2 打的感觉还是有点崩QAQ,C 题因为忘开 long long WA 了一发,D2 根号分治+ two-pointers 被卡空间,E 题只会个可持久化并查集+二分,时间也过不去。

还是太蒻啦>_<

Analysis

(文中称题目中要求的 \(k\) 为前缀边数)

首先我们需要了解一个叫做 Kruskal 重构树的东西 qwq。

这个东西是我们快速判断两点连通所需前缀边数的关键。

考虑一个询问 \((l,r)\),显然 \(l=r\) 时答案为 \(0\)

\(l \lt r\),考虑简化一下询问内容:

\(f(i)\) 表示点 \((i,i+1)\) 连通所需的最小前缀边数,则询问 \((l,r)\) 的答案即为 \(\max\{f(l),f(l+1)\ldots f(r-1)\}\)

(莫名想起后缀数组awa)

这个转化理解起来并不难,但考场上真想不出来QAQ

显然是一个 RMQ 问题,只需要考虑如何计算每个 \(f(i)\) 即可。

这时要用到上述的 Kruskal 重构树来解决。

设第 \(i\) 条边的权值为 \(i\),跑出 MST,那么 \(f(i)\) 即为 \((i,i+1)\) 两点在 MST 上的简单路径上边权的最大值。

正确性不难理解,由于 Kruskal 是贪心构造,\((i,i+1)\) 间简单路径上的边权必然已经最小化。

那么直接跑个倍增就好了。

(官方题解里说好像可以不用倍增,直接用 dsu 处理,但看了会没看太明白,大佬们可以给我这个小蒟蒻讲讲QAQ)

Code

多组样例跑倍增有点烦,因为数组没清干净 WA 了一发 qwq。

// Problem: E. Qpwoeirut and Vertices
// Contest: Codeforces - Codeforces Round #809 (Div. 2)
// URL: https://codeforces.com/contest/1706/problem/E
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
#define pb emplace_back
using namespace std;
const int maxn = 2e5 + 5;
typedef long long ll;
typedef pair<int,int> pii;
int n,m,Q,pre[maxn];
vector<pii> G[maxn];
int find(int x) {
	return x == pre[x] ? x : pre[x] = find(pre[x]);
}
struct Edge {
	int u,v,t;
	Edge() {
		u = v = t = 0;
	}
	Edge(int u,int v,int t):u(u),v(v),t(t){}
}E[maxn];
int f[maxn][20],lg[maxn],d[maxn],p[maxn][20];
void dfs(int u,int fa) {
	for(auto& [v , w] : G[u]) {
		if(v == fa)continue ;
		f[v][0] = u;
		d[v] = d[u] + 1;
		p[v][0] = w;
		for(int k = 1;(1 << k) <= d[v];++ k) {
			f[v][k] = f[f[v][k - 1]][k - 1];
			p[v][k] = max(p[v][k - 1] , p[f[v][k - 1]][k - 1]);
		}
		dfs(v , u);
	}
	return ;
}
int LCA(int u,int v) {
	if(d[u] < d[v])swap(u , v);
	int ans = 0;
	for(int k = lg[d[u]];~ k;-- k) {
		if(d[u] - (1 << k) >= d[v]) {
			ans = max(ans , p[u][k]);
			u = f[u][k];
		}
		if(u == v)return ans;
	}
	for(int k = lg[d[u]];~ k;-- k) {
		if(f[u][k] != f[v][k]) {
			ans = max(ans , max(p[u][k] , p[v][k]));
			u = f[u][k];
			v = f[v][k];
		}
	}
	return max(ans , max(p[u][0] , p[v][0]));
}
int dp[maxn][20];
int RMQ(int l,int r) {
	int k = lg[r - l + 1];
	return max(dp[l][k] , dp[r - (1 << k) + 1][k]);
}
void work() {
	scanf("%d %d %d",&n,&m,&Q);
	for(int i = 1;i <= m;++ i) {
		scanf("%d %d",&E[i].u,&E[i].v);
		E[i].t = i;
	}
	for(int i = 1;i <= n;++ i)pre[i] = i,G[i].clear();
	for(int i = 2;i <= n;++ i)lg[i] = lg[i >> 1] + 1;
	for(int i = 1;i <= m;++ i) {
		if(find(E[i].u) == find(E[i].v))continue ;
		pre[find(E[i].u)] = find(E[i].v);
		G[E[i].u].pb(E[i].v , E[i].t);
		G[E[i].v].pb(E[i].u , E[i].t);
	}
	for(int i = 1;i <= n;++ i) {
		d[i] = 0;
		for(int k = 0;(1 << k) <= n;++ k) {
			dp[i][k] = p[i][k] = f[i][k] = 0;
		}
	}
	dfs(1 , d[1] = 0);
	for(int i = 1;i < n;++ i)dp[i][0] = LCA(i , i + 1);
	for(int k = 1;(1 << k) < n;++ k) {
		for(int i = 1;i + (1 << k) <= n;++ i) {
			dp[i][k] = max(dp[i][k - 1] , dp[i + (1 << k - 1)][k - 1]);
		}
	}
	while(Q --) {
		int l,r;
		scanf("%d %d",&l,&r);
		if(l == r)printf("0 ");
		else printf("%d ",RMQ(l , r - 1));
	}
	puts("");
	return ;
}
int main() {
	int T;
	scanf("%d",&T);
	while(T --)work();
	return 0;
}

完结撒花✿✿ヽ(°▽°)ノ✿

posted @ 2022-07-19 12:03  ImALAS  阅读(84)  评论(0编辑  收藏  举报