题目链接:CF 或者 洛谷

官解看上去挺智慧的,来点朴素的解法。我们来当做纯 ds 题去做。首先明确一点,图中若干个点关于最早连通性的这个问题可以考虑 \(MST\),我们有一类东西叫 \(kruskal\) 重构树。这玩意其实只需要记住它的性质和建树方式即可,证明其实也是比较简单的,基于 \(kruskal\) 的构造 \(MST\) 过程反证法即可。

先说说这棵树有啥性质:

原图中两个点间所有路径上的边最大权值的最小值 \(=\) 最小生成树上两点简单路径的边最大权值 \(=\ Kruskal\) 重构树上两点 \(LCA\) 的点权。其实这玩意和笛卡尔树差不多,将 \(RMQ\) 问题变为树上的 \(LCA\) 问题。证明方式就因为你是按边权的从小到大去 \(merge\) 的,比较好证明出 \(MST\) 是具有以上性质的。

建树方式:

在 \(MST\) 的建造过程中,如果有两个点可以连通,我们就构造一个新的点,通常为 \(n+x\) 作为新点编号,然后作为它俩的 \(LCA\),并为这个点赋予点权,其中点权为原来的边权,说直白点就是化边为点。并且此时,还需要将它们对应并查集的 \(fa\) 指向新点,同时建边。

回到本题

本题它有啥用,注意到本题每个边其实也是有边权的,边权其实为边的编号,最终问的是 \([l,r]\) 上的所有点连通时,\(1 \sim id\) 编号的边是已经加入了的,\(id\) 为满足的最小编号的边。先考虑一个简单情形:\(i\) 与 \(i+1\) 连通时加入的边即为它们的 \(LCA\)。这是为啥?在 \(kruskal\) 上每个新点其实就是代表着一条边,如果这两个点之间有边,显然就是 \(LCA\),没有的话,显然等价于它们所在的连通分量的代表元连通之时,而它们的代表元联通以后形成的新点即为 \(LCA\):

容易看出 \(x\) 和 \(y\) 连通的时间即为 \(x'\) 与 \(y'\) 之间的连边成为 \(T\) 这个点时也即为 \(x\) 和 \(y\) 的 \(LCA\)。

那么知道两个点的最早连通时间了,即为 \(LCA\) 的点权,因为本题里点权即为边的编号,如果是一堆点连通的最早时呢?显而易见是它们所有的 \(LCA\) 的点权。求一大堆点的 \(lca\) 怎么做:参考文章。其实就是这一堆点的 \(dfn\) 序最大和最小的两个点的 \(LCA\)。至此问题就全部解决了。

最终算法框架

首先边权即为边的编号,而编号显然由小到大输入,所以我们不需要排序了,对此建立 \(kruskal\) 重构树。然后跑 \(dfs\) 得到 \(dfn\) 序以及 \(lca\) 的倍增数组。然后由于需要查询区间上最大和最小的 \(dfn\) 序为多少,然后再拿到点,所以这个 \(RMQ\) 问题我们直接两个 \(ST\) 表就能解决。最后查询这两个点的 \(LCA\) 的点权即为所求的最早边的编号。当然注意多测清空和初始化问题。

参照代码
#include <bits/stdc++.h>

// #pragma GCC optimize(2)
// #pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
// #pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native") // #define isPbdsFile #ifdef isPbdsFile #include <bits/extc++.h> #else #include <ext/pb_ds/priority_queue.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/trie_policy.hpp>
#include <ext/pb_ds/tag_and_trait.hpp>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/list_update_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/exception.hpp>
#include <ext/rope> #endif using namespace std;
using namespace __gnu_cxx;
using namespace __gnu_pbds;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef tuple<int, int, int> tii;
typedef tuple<ll, ll, ll> tll;
typedef unsigned int ui;
typedef unsigned long long ull;
typedef __int128 i128;
#define hash1 unordered_map
#define hash2 gp_hash_table
#define hash3 cc_hash_table
#define stdHeap std::priority_queue
#define pbdsHeap __gnu_pbds::priority_queue
#define sortArr(a, n) sort(a+1,a+n+1)
#define all(v) v.begin(),v.end()
#define yes cout<<"YES"
#define no cout<<"NO"
#define Spider ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
#define MyFile freopen("..\\input.txt", "r", stdin),freopen("..\\output.txt", "w", stdout);
#define forn(i, a, b) for(int i = a; i <= b; i++)
#define forv(i, a, b) for(int i=a;i>=b;i--)
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define endl '\n'
//用于Miller-Rabin
[[maybe_unused]] static int Prime_Number[13] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37}; template <typename T>
int disc(T* a, int n)
{
return unique(a + 1, a + n + 1) - (a + 1);
} template <typename T>
T lowBit(T x)
{
return x & -x;
} template <typename T>
T Rand(T l, T r)
{
static mt19937 Rand(time(nullptr));
uniform_int_distribution<T> dis(l, r);
return dis(Rand);
} template <typename T1, typename T2>
T1 modt(T1 a, T2 b)
{
return (a % b + b) % b;
} template <typename T1, typename T2, typename T3>
T1 qPow(T1 a, T2 b, T3 c)
{
a %= c;
T1 ans = 1;
for (; b; b >>= 1, (a *= a) %= c)if (b & 1)(ans *= a) %= c;
return modt(ans, c);
} template <typename T>
void read(T& x)
{
x = 0;
T sign = 1;
char ch = getchar();
while (!isdigit(ch))
{
if (ch == '-')sign = -1;
ch = getchar();
}
while (isdigit(ch))
{
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
x *= sign;
} template <typename T, typename... U>
void read(T& x, U&... y)
{
read(x);
read(y...);
} template <typename T>
void write(T x)
{
if (typeid(x) == typeid(char))return;
if (x < 0)x = -x, putchar('-');
if (x > 9)write(x / 10);
putchar(x % 10 ^ 48);
} template <typename C, typename T, typename... U>
void write(C c, T x, U... y)
{
write(x), putchar(c);
write(c, y...);
} template <typename T11, typename T22, typename T33>
struct T3
{
T11 one;
T22 tow;
T33 three; bool operator<(const T3 other) const
{
if (one == other.one)
{
if (tow == other.tow)return three < other.three;
return tow < other.tow;
}
return one < other.one;
} T3() { one = tow = three = 0; } T3(T11 one, T22 tow, T33 three) : one(one), tow(tow), three(three)
{
}
}; template <typename T1, typename T2>
void uMax(T1& x, T2 y)
{
if (x < y)x = y;
} template <typename T1, typename T2>
void uMin(T1& x, T2 y)
{
if (x > y)x = y;
} constexpr int N = 5e5 + 10;
int n, m, q;
vector<int> kruskal[N]; //kruskal重构树
int val[N]; struct
{
int tot;
int fa[N]; void init()
{
forn(i, 1, tot)kruskal[i].clear(), val[i] = 0;
forn(i, 1, n<<1)fa[i] = i;
tot = n;
} int find(const int x)
{
return x == fa[x] ? x : fa[x] = find(fa[x]);
} void merge(int x, int y, const int id)
{
x = find(x), y = find(y);
if (x != y)
{
val[++tot] = id;
fa[x] = fa[y] = tot;
kruskal[tot].push_back(x), kruskal[tot].push_back(y);
kruskal[x].push_back(tot), kruskal[y].push_back(tot);
}
}
} Kruskal; constexpr int T = 25;
int cnt;
int fa[N][T + 1], dfn[N], rev[N], deep[N]; inline void dfs(const int curr, const int pa)
{
dfn[curr] = ++cnt;
rev[cnt] = curr;
deep[curr] = deep[fa[curr][0] = pa] + 1;
forn(i, 1, T)fa[curr][i] = fa[fa[curr][i - 1]][i - 1];
for (const auto nxt : kruskal[curr])if (nxt != pa)dfs(nxt, curr);
} inline int LCA(int x, int y)
{
if (deep[x] < deep[y])swap(x, y);
forv(i, T, 0)if (deep[fa[x][i]] >= deep[y])x = fa[x][i];
if (x == y)return x;
forv(i, T, 0)if (fa[x][i] != fa[y][i])x = fa[x][i], y = fa[y][i];
return fa[x][0];
} //查询[1,n]上最大和最小的dfn序
int stMax[N][T], stMin[N][T]; inline void init()
{
int k = log2(n) + 1;
forn(i, 1, n)stMax[i][0] = stMin[i][0] = dfn[i];
forn(j, 1, k)
{
forn(i, 1, n-(1<<j)+1)
{
stMax[i][j] = max(stMax[i][j - 1], stMax[i + (1 << j - 1)][j - 1]);
stMin[i][j] = min(stMin[i][j - 1], stMin[i + (1 << j - 1)][j - 1]);
}
}
} inline int query(const int l, const int r, const bool isMax = false)
{
const int k = log2(r - l + 1);
return isMax ? max(stMax[l][k], stMax[r - (1 << k) + 1][k]) : min(stMin[l][k], stMin[r - (1 << k) + 1][k]);
} inline void clear()
{
forn(i, 1, cnt)dfn[i] = rev[i] = 0;
cnt = 0;
} inline void solve()
{
cin >> n >> m >> q;
Kruskal.init();
forn(i, 1, m)
{
int u, v;
cin >> u >> v;
Kruskal.merge(u, v, i);
}
clear();
dfs(Kruskal.tot, 0); //遍历krusal重构树
init(); //初始化ST表
forn(i, 1, q)
{
int l, r;
cin >> l >> r;
if (l == r)cout << 0 << ' ';
else
{
const int L = rev[query(l, r)], R = rev[query(l, r, true)]; //rev[dfn[i]]=i
cout << val[LCA(L, R)] << ' '; //点权即为边的编号
}
}
cout << endl;
} signed int main()
{
// MyFile
Spider
//------------------------------------------------------
// clock_t start = clock();
int test = 1;
// read(test);
cin >> test;
forn(i, 1, test)solve();
// while (cin >> n, n)solve();
// while (cin >> test)solve();
// clock_t end = clock();
// cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl;
}
\[
时间复杂度为:\ O((n+q)\log{n})
\]

当然,如果涉及到动态 \(MST\) 问题,我们用 \(LCT\) 去维护这棵重构树即可,原理一致。

CF1706E Qpwoeirut and Vertices 题解的更多相关文章

  1. Codeforces Round #809 (Div. 2) A-E

    Codeforces Round #809 (Div. 2) 2022/7/19 下午VP 传送门:https://codeforces.com/contest/1706 A. Another Str ...

  2. leetcode & lintcode 题解

    刷题备忘录,for bug-free 招行面试题--求无序数组最长连续序列的长度,这里连续指的是值连续--间隔为1,并不是数值的位置连续 问题: 给出一个未排序的整数数组,找出最长的连续元素序列的长度 ...

  3. 【bzoj1001】【最短路】【对偶图】【最大流转最小割】狼抓兔子题解

    [BZOJ1001]狼抓兔子 1001: [BeiJing2006]狼抓兔子 Time Limit: 15 Sec  Memory Limit: 162 MBSubmit: 18872  Solved ...

  4. codeforces 920 EFG 题解合集 ( Educational Codeforces Round 37 )

    E. Connected Components? time limit per test 2 seconds memory limit per test 256 megabytes input sta ...

  5. Codeforces Round #467 Div.2题解

    A. Olympiad time limit per test 1 second memory limit per test 256 megabytes input standard input ou ...

  6. Codeforces Round #198 (Div. 2)C,D题解

    接着是C,D的题解 C. Tourist Problem Iahub is a big fan of tourists. He wants to become a tourist himself, s ...

  7. 2018 Multi-University Training Contest 3(部分题解)

    Problem F. Grab The Tree Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 524288/524288 K (Ja ...

  8. 300iq Contest 1 简要题解

    300iq Contest 1 简要题解 咕咕咕 codeforces A. Angle Beats description 有一张\(n\times m\)的方阵,每个位置上标有*,+,.中的一种. ...

  9. 2019秋季PAT甲级_C++题解

    2019 秋季 PAT (Advanced Level) C++题解 考试拿到了满分但受考场状态和知识水平所限可能方法不够简洁,此处保留记录,仍需多加学习.备考总结(笔记目录)在这里 7-1 Fore ...

  10. ICPC — International Collegiate Programming Contest Asia Regional Contest, Yokohama, 2018–12–09 题解

    目录 注意!!此题解存在大量假算法,请各位巨佬明辨! Problem A Digits Are Not Just Characters 题面 题意 思路 代码 Problem B Arithmetic ...

随机推荐

  1. 【每日一题】16.Treepath (LCA + DP)

    补题链接:Here 题意总结:寻找有多少条两个点之间偶数路径 看完题,很容易想到在树型中,同一层的节点必然是偶数路径到达,还有就是每隔两层的节点一样可以到达,所以我就理所应当的写了如下代码 using ...

  2. Linux Page Cache调优在Kafka中的应用

    本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/MaeXn-kmgLUah78brglFkg作者:Yang Yijun 本文主要描述Linux ...

  3. 3 分钟创建 Serverless Job 定时获取新闻热搜

    不用掏手机,不用登微博,借助 SAE 定时任务就可以实现每小时获取实时新闻热搜!SAE 场景体验火热开启中,参与还可领好礼! Job 作为一种运完即停的负载类型,在企业级开发中承载着丰富的使用场景.S ...

  4. C# 从桌面右下角显示 Popup 窗口提醒

    上图演示 private void display_Click(object sender, EventArgs e) { Frm_Info.Instance().ShowForm();//显示窗体 ...

  5. python之configparser类的使用

    一.定义配置文件格式如下:data.conf [interface] url=http://192.168.37.8:7777/api/mytest2 [switch] switch_car=on [ ...

  6. Jupyter Notebook报错'500 : Internal Server Error'的解决方法

    问题根因 Jupyter相关的软件包版本匹配存在问题,或者历史上安装过Jupyter相关的配套软件但是有残留.大部分网上的博客都是推荐用pip重装jupyter或者nbconvert,亲测无法解决该问 ...

  7. java - for循环 排序数组 - 求数组最小值

    主要是利用静态变量存储 public class Bubble2 { static int minNumber; public static void main(String[] args) { in ...

  8. js - 元素 scrollTop 设置无效的原因 及 解决办法

    原因 :  元素 display : flex ; 解决方法 : display : block;

  9. Linux-磁盘-di-目录查询-du-tree

  10. Nginx arm编译安装

    Nginx arm编译安装 背景 计划编译一套产品. 能够比较方便快捷的进行 nginx的交付. 主要思想是源码编译 不仅能够在arm上面运行 也可以在x86上面编译 考虑性能还有一些扩展性. 高效处 ...