题目大意:给定 N 个点,M 条边的无向图,支持两种操作:动态删边和查询任意两点之间路径上边权的最大值最小是多少。

题解:

引理:对原图求最小生成树,可以保证任意两点之间的路径上边权的最大值取得最小值。

证明:任取两点 x, y,若 x, y 的路径上最大值最小的边不在最小生成树的路径上,可以将那条边加入最小生成树中,并删去由这条边的加入所带来的环中边权最大的那条边,可以使得最小生成树更小,产生矛盾,证毕。

有了引理之后,问题转化成了维护支持动态删边的最小生成树。发现删边可能会导致最少生成树不断发生变化,每次变化都需要重构生成树,时间复杂度较高。可以采用离线询问,转化成倒序加边的最小生成树的维护,每次加一条边时,只需删除环上最大的那条边即可。支持动态加边和删边,可以采用 lct 进行维护。最后,可以进行点边转化,即:将边缩为一个点,边权为点权,点的点权为 0 即可。

代码如下

#include <bits/stdc++.h>

using namespace std;

struct edge {
int x, y, z;
bool has;
bool operator<(const edge &rhs) {
return this->z < rhs.z;
}
}; struct UFS {
vector<int> f;
UFS(int n) {
f.resize(n + 1);
for (int i = 1; i <= n; i++) {
f[i] = i;
}
}
int find(int x) {
return x == f[x] ? x : f[x] = find(f[x]);
}
bool merge(int x, int y) {
x = find(x), y = find(y);
if (x != y) {
f[x] = y;
return 1;
}
return 0;
}
}; struct node {
node *l, *r, *p;
int val, maxv, rev, id;
node (int _val = 0, int _id = 0) {
l = r = p = NULL;
val = maxv = _val;
id = _id;
rev = 0;
}
void unsafe_reverse() {
swap(l, r);
rev ^= 1;
}
void pull() {
maxv = val;
if (l != NULL) {
l->p = this;
maxv = max(maxv, l->maxv);
}
if (r != NULL) {
r->p = this;
maxv = max(maxv, r->maxv);
}
}
void push() {
if (rev) {
if (l != NULL) {
l->unsafe_reverse();
}
if (r != NULL) {
r->unsafe_reverse();
}
rev = 0;
}
}
};
bool is_root(node *v) {
if (v == NULL) {
return false;
}
return (v->p == NULL) || (v->p->l != v && v->p->r != v);
}
void rotate(node *v) {
node *u = v->p;
assert(u != NULL);
v->p = u->p;
if (v->p != NULL) { // work with father
if (u == v->p->l) {
v->p->l = v;
}
if (u == v->p->r) {
v->p->r = v;
}
}
if (v == u->l) {
u->l = v->r;
v->r = u;
}
if (v == u->r) {
u->r = v->l;
v->l = u;
}
u->pull();
v->pull();
}
void deal_with_push(node *v) {
static stack<node*> stk;
while (true) {
stk.push(v);
if (is_root(v)) {
break;
}
v = v->p;
}
while (!stk.empty()) {
stk.top()->push();
stk.pop();
}
}
void splay(node *v) {
deal_with_push(v);
while (!is_root(v)) {
node *u = v->p;
if (!is_root(u)) {
if ((u->p->l == u) ^ (u->l == v)) {
rotate(v);
} else {
rotate(u);
}
}
rotate(v);
}
}
void access(node *v) {
node *u = NULL;
while (v != NULL) {
splay(v);
v->r = u;
v->pull();
u = v;
v = v->p;
}
}
void make_root(node *v) {
access(v);
splay(v);
v->unsafe_reverse();
}
node *find_root(node *v) {
access(v);
splay(v);
while (v->l != NULL) {
v->push();
v = v->l;
}
splay(v);
return v;
}
void split(node *u, node *v) {
make_root(u);
access(v);
splay(v);
}
void link(node *u, node *v) {
if (find_root(u) == find_root(v)) {
return;
}
make_root(v);
v->p = u;
}
void cut(node *u, node *v) {
make_root(u);
if (find_root(v) == u && v->p == u && v->l == NULL) {
v->p = u->r = NULL;
u->pull();
}
}
node* find(node *v, int val) {
while (true) {
if (v->val == val) {
break;
}
if (v->l != NULL && v->l->maxv == val) {
v = v->l;
} else {
v = v->r;
}
}
return v;
} int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int n, m, q;
cin >> n >> m >> q;
vector<node*> t(n + m + 1);
for (int i = 1; i <= n; i++) {
t[i] = new node(0, i);
}
vector<edge> e(m + 1);
map<pair<int, int>, int> id;
for (int i = 1; i <= m; i++) {
int x, y, z;
cin >> x >> y >> z;
if (x > y) {
swap(x, y);
}
e[i] = {x, y, z, 1};
id[{x, y}] = i;
}
vector<pair<int, pair<int, int>>> events(q + 1);
for (int i = 1; i <= q; i++) {
int opt, x, y;
cin >> opt >> x >> y;
if (x > y) {
swap(x, y);
}
events[i] = {opt, {x, y}};
if (opt == 2) {
e[id[{x, y}]].has = 0;
}
}
auto kruskal = [&](vector<edge> ee) {
sort(ee.begin() + 1, ee.end());
UFS ufs(n);
for (int i = 1; i <= m; i++) {
if (ee[i].has == 1) {
int x = ee[i].x, y = ee[i].y, z = ee[i].z;
if (ufs.merge(x, y) == 1) {
int _id = id[{x, y}] + n; // edge_id
t[_id] = new node(z, _id);
link(t[x], t[_id]);
link(t[y], t[_id]);
}
}
}
};
kruskal(e);
vector<int> ans;
for (int i = q; i >= 1; i--) {
int x = events[i].second.first;
int y = events[i].second.second;
int eid = id[{x, y}];
int z = e[eid].z;
if (events[i].first == 1) {
split(t[x], t[y]);
ans.push_back(t[y]->maxv);
} else {
split(t[x], t[y]);
if (t[y]->maxv > z) {
int _id = find(t[y], t[y]->maxv)->id; // edge id
int a = e[_id - n].x, b = e[_id - n].y;
cut(t[a], t[_id]);
cut(t[b], t[_id]);
t[eid + n] = new node(z, eid + n);
link(t[x], t[eid + n]);
link(t[y], t[eid + n]);
}
}
}
reverse(ans.begin(), ans.end());
for (auto v : ans) {
cout << v << endl;
}
return 0;
}

【洛谷P4172】水管局长的更多相关文章

  1. 洛谷P4172 [WC2006]水管局长 (LCT,最小生成树)

    洛谷题目传送门 思路分析 在一个图中,要求路径上最大边边权最小,就不难想到最小生成树.而题目中有删边的操作,那肯定是要动态维护啦.直接上LCT维护边权最小值(可以参考一下蒟蒻的Blog) 这时候令人头 ...

  2. 洛谷P4172 [WC2006]水管局长(lct求动态最小生成树)

    SC省MY市有着庞大的地下水管网络,嘟嘟是MY市的水管局长(就是管水管的啦),嘟嘟作为水管局长的工作就是:每天供水公司可能要将一定量的水从x处送往y处,嘟嘟需要为供水公司找到一条从A至B的水管的路径, ...

  3. [洛谷P4172] WC2006 水管局长

    问题描述 SC省MY市有着庞大的地下水管网络,嘟嘟是MY市的水管局长(就是管水管的啦),嘟嘟作为水管局长的工作就是:每天供水公司可能要将一定量的水从x处送往y处,嘟嘟需要为供水公司找到一条从A至B的水 ...

  4. 【Luogu】P4172水管局长(LCT)

    题目链接 有个结论是x到y的路径上最长边权值等于最小生成树上最长边权值,于是问题转化为最小生成树. 再考虑把问题反过来,删边变成加边. 于是变成动态维护最小生成树,LCT可以做到. #include& ...

  5. 【洛谷4172】 [WC2006]水管局长(LCT)

    传送门 洛谷 BZOJ Solution 如果不需要动态的话,那就是一个裸的最小生成树上的最大边权对吧. 现在动态了的话,把这个过程反着来,就是加边对吧. 现在问题变成了怎么动态维护加边的最小生成树, ...

  6. 洛谷.4172.[WC2006]水管局长(LCT Kruskal)

    题目链接 洛谷(COGS上也有) 不想去做加强版了..(其实处理一下矩阵就好了) 题意: 有一张图,求一条x->y的路径,使得路径上最长边尽量短并输出它的长度.会有<=5000次删边. 这 ...

  7. P4172 [WC2006]水管局长(LCT)

    P4172 [WC2006]水管局长 LCT维护最小生成树,边权化点权.类似 P2387 [NOI2014]魔法森林(LCT) 离线存储询问,倒序处理,删边改加边. #include<iostr ...

  8. P4172 [WC2006]水管局长

    P4172 [WC2006]水管局长 前言 luogu数据太小 去bzoj,他的数据大一些 思路 正着删不好维护 那就倒着加,没了 LCT维护他的最小生成树MST 树上加一条边肯定会有一个环 看看环上 ...

  9. P4172 [WC2006]水管局长 LCT维护最小生成树

    \(\color{#0066ff}{ 题目描述 }\) SC 省 MY 市有着庞大的地下水管网络,嘟嘟是 MY 市的水管局长(就是管水管的啦),嘟嘟作为水管局长的工作就是:每天供水公司可能要将一定量的 ...

随机推荐

  1. IntelliJ IDEA 2019.2.1 破解教程, 最新激活码(激活到2089年8月,亲测有效,持续更新中...)

    当前最新版本 IDEA 2019.2.1 本来笔者这边是有个正版激活码可以使用的,但是,2019.9月3号的时候,一些小伙伴反映这个注册码已经失效了,于是拿着自己的 IDEA, 赶快测试了一下,果不其 ...

  2. python手撸桌面计算器

    网上有一些许多关于计算器的源码,但我似乎不太care 一直寻思着自己手撸一个才有意思,于是这就开始了 实现功能: 1.基本的两个数 +-x÷ 运算以及取反,百分之,平方等 2.支持连续运算 3.暂不支 ...

  3. 学习笔记:CentOS 7学习之十二:查找命令

    目录 1.which-whereis-locate-grep-find查找命令 1.1 which 1.2 whereis 1.3 locate 1.4 grep 1.5 find命令 2. 命令的判 ...

  4. bzoj 4736: 温暖会指引我们前行 (LCT 维护最大生成树)

    链接:https://www.lydsy.com/JudgeOnline/problem.php?id=4736 题面: 寒冬又一次肆虐了北国大地 无情的北风穿透了人们御寒的衣物 可怜虫们在冬夜中发出 ...

  5. 小米soar

    SOAR 简介 SOAR,即 SQL Optimizer And Rewriter,是一款 SQL 智能优化与改写工具,由小米运维 DBA 团队出品 SOAR 体系架构 SOAR主要由语法解析器,集成 ...

  6. 15-Perl 格式化输出

    1.Perl 格式化输出Perl 是一个非常强大的文本数据处理语言.Perl 中可以使用 format 来定义一个模板,然后使用 write 按指定模板输出数据.Perl 格式化定义语法格式如下:fo ...

  7. SQL Server中的锁可以分为如下几类

    从大类来看,SQL Server中的锁可以分为如下几类: 共享锁(S锁):用于读取资源所加的锁.拥有共享锁的资源不能被修改.共享锁默认情况下是读取了资源马上被释放.比如我读100条数据,可以想像成读完 ...

  8. 安装多个ORACLE导致多个Oracle HOME的情况!

    Oracle由于版本的不同,在注册表中产生的注册表信息也有所不同,但主要的键值信息还是一样的,例如Oracle10g比oracle9i在注册表中表现的更为“简洁”,在未知的情况下,获取Oracle10 ...

  9. opencv 单目标模板匹配(只适用于模板与目标尺度相同)

    #include <iostream> #include "opencv/cv.h" #include "opencv/cxcore.h" #inc ...

  10. 这不是javascript:什么?

    javascript协议.<a href=“javascript:void(0):”>xxx</a>基于事件的事件,例如:<input onblur=“check():” ...