题目大意:给定 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. js 返回一个数组里面0出现的次数

    var num = new Array(10000).fill('').map((item,index) => (index + 1)). 在点号后面补充代码,让num是这个数组中0出现的次数, ...

  2. Spring Boot + Vue 跨域请求问题

    使用Spring Boot + Vue 做前后端分离项目搭建,实现登录时,出现跨域请求 Access to XMLHttpRequest at 'http://localhost/open/login ...

  3. vi操作笔记一

    vi命令  gg 到首行 shift + 4 跳到该行最后一个字符 shift + 6 跳到该行首个字符 shift + g 到尾行 vi 可视 G 全选 = 程序对齐   gg 到首行 vi 可视  ...

  4. java23种设计模式之四:建造者模式

    在软件开发过程中有时需要创建一个复杂的对象,这个复杂对象通常由多个子部件按一定的步骤组合而成.例如:在新招收一个员工时,对个人信息对象的创建,在不同的阶段,需要个人信息的内容也不一样,姓名.性别.年龄 ...

  5. 【Python】【demo实验21】【练习实例】【求球反弹高度】

    原题: 一球从100米高度自由落下,每次落地后反跳回原高度的一半:再落下,求它在第10次落地时,共经过多少米?第10次反弹多高? 我的源码: #!/usr/bin/python # encoding= ...

  6. 使用SecureCRT连接虚拟机中Linux系统 和 虚拟机网络配置

    使用SecureCRT连接步骤:1.首先打开虚拟机,点击左上角的编辑,再点击虚拟网络编辑器(已经进行虚拟网络编辑的忽略此步骤,直接进行第二步) 点击VMnet8网络,点击更改设置,此步骤需要管理员权限 ...

  7. C++学习 之 程序的组成部分(部分知识笔记)

    1.预处理器编译指令#include: 预处理器是在程序编译前运行的工具.预处理器编译指令是向预处理器发送的命令,总是以#为标识,include便是其中常见的一种,用于引用文件,比如:iostream ...

  8. 超级实用的 Java 工具类

    超级实用的 Java 工具类 在Java中,工具类定义了一组公共方法,这篇文章将介绍Java中使用最频繁及最通用的Java工具类.以下工具类.方法按使用流行度排名,参考数据来源于Github上随机选取 ...

  9. windows server 2008 R2 Enterprise 防火墙开启允许远程桌面登录

    解决方法: 开始------ > 运行 ----- > gpedit.msc 打开“本地组策略编辑器”,按如下设置:计算机配置----->管理模板----->网络-----&g ...

  10. 3-Perl 基础语法

    Perl 基础语法Perl借用了C.sed.awk.shell脚本以及很多其他编程语言的特性,语法与这些语言有些类似,也有自己的特点.Perl 程序有声明与语句组成,程序自上而下执行,包含了循环,条件 ...