题目大意:给定 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. Spring源码分析(1)容器的基本实现——核心类介绍

    bean是Spring中最核心的东西,因为Spring就像是个大水桶,而bean就像是容器中的水,水桶脱离了水便也没什么用处了,那么我们先看看bean的定义. public class MyTestB ...

  2. mysq的慢查询日志

    MySQL 慢查询日志是排查问题 SQL 语句,以及检查当前 MySQL 性能的一个重要功能. 查看是否开启慢查询功能: mysql> show variables like 'slow_que ...

  3. 手把手教你用vue-clic3搭建vue-element-admin项目

    下载element-admin框架 点击该地址:https://github.com/PanJiaChen/vue-element-admin 用git clone https://github.co ...

  4. [转帖]公钥基础设施(PKI)/CFSSL证书生成工具的使用

    公钥基础设施(PKI)/CFSSL证书生成工具的使用 weilovepan520关注1人评论84344人阅读2018-05-26 12:22:20 https://blog.51cto.com/liu ...

  5. python+pycharm+PyQt5 图形化界面安装教程

    python图形化界面安装教程 配置环境变量 主目录 pip所在目录,及script目录 更新pip(可选) python -m pip install --upgrade pip ps:更新出错一般 ...

  6. Golang Module快速入门

    前言: 在Golang1.11之前的版本中,官方没有提供依赖和包管理工具.开发者通常会使用vendor或者glide的方式来管理依赖(也有直接使用GOPATH多环境方式),而在Golang1.11之后 ...

  7. django channels

    django channels django channels 是django支持websocket的一个模块. 1. 安装 `pip3 install channels` 2. 快速上手 2.1 在 ...

  8. linux常用国内的免费源及其各别的配置方法.阿里源,epel源,搜狐网易等等..

    国内的一些开源的平台收集的源,确实给我们提供了很多便利,所以我就稍微收集整理了一些常用的源和网址,我也不确定能用到什么时候!欢迎评论区留言! 搜狐开源镜像站 http://mirrors.sohu.c ...

  9. Python学习笔记:流程控制

    单分支: if 条件: 满足条件后执行的代码 程序举例: leiyu=28if leiyu > 22: print("You can find girl friend..." ...

  10. INPUT和CONSTRUCT指令——范例报表查询,作用让用户输入数据,自动生成SQL的WHERE条件,带开窗查询

    INPUT指令 说明:1. 当程序执行到INPUT指令时,会将控制权交给用户,让用户输入数据.2. 用户输入完字段的数据,会将数据回传给程序中的变量接收.3. 只要执行到INPUT的指令,程序会将每个 ...