[CF1706E]Qpwoeirut and Vertices 题解
upd:这篇文章中 Kruskal 重构树的写法是我在没学过的时候瞎胡的,并不标准,感兴趣可以来这篇文章学习一下。
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;
}
完结撒花✿✿ヽ(°▽°)ノ✿