题意

给出一个\(n\)个点\(m\)条边的无向图,给出每个点的初始点权,\(q\)次操作:

  • 修改一个点的点权
  • 询问两点间的所有路径中最小点权最小的路径上的最小点权

\(n,m,q\le 10^5,w\le 10^9\)

分析

一个结论

一个经典的结论是:一个点数大于等于3的点双连通分量中对于任意不同的三点\(a,b,c\),必定存在一条简单路径从\(a\)走到\(b\)经过\(c\) 。

证明

(来源:codeforces题解中的证明)

首先点双连通分量的定义是,任意两个不同点之间必定存在至少两条点不重复的简单路径(首位不算)。也就是说,在点双连通分量中,任意删去一个点,这个连通分量仍然是一个连通块。

边双连通分量的定义是任意两个不同点之间必定存在至少两条边不重复简单路径,即删去任意一条边连通分量仍然是一个连通块。

很明显,点双连通分量是包含边双连通分量的,所以在点双连通分量中删去一条边也不会导致分量不连通(考虑删去这条边的任意一个端点)。

清楚定义后,我们就可以开始证明上面的结论了。

构造网络。对原图中的点进行拆点,\(u\)变成两个点\(u_1,u_2\),连边\((u_1,u_2,1)\),表示一个点的容量为1,即只能经过一次。对于原图中原有的边\((u,v)\),连边\((u_2,v_1,1)\),表示这条边也只能经过一次。对于任意选定的三点\(a,b,c\),连边:\((S,c_1,2),(a_2,T,1),(b_2,T,1)\),那么若这个网络的最大流为2,就说明这样一条简单路径存在。

由最大流最小割定理,要证明这个网络的最大流一定为2,只要证明网络的最小割一定为2 。考虑这个网络的最小割\(C\),从连边可以得到\(C\le 2\),接下来证明\(C>1\) 。

割掉\((S,c_1,2)\)会让\(C=2\),割掉\((a_2,T,1)\)或\((b_2,T,1)\)都不足以让网络不连通。剩下的有两种边,一种是拆点拆出来的边,一种是原图中有的边。对于边\((u_1,u_2,1)\),我们割掉这条边相当于是删掉这个点,而由点双连通分量的定义可知,删掉任意一个点原图仍然连通,故\(C>1\)。另一种边是\((u_2,v_1,1)\),由点双连通分量包含边双连通分量的性质可知,删去任意一条边原图仍然连通,故\(C>1\) 。综上,我们有\(C\le 2,C>1\),又由连边可知\(C\in \mathbb Z^+\),所以\(C=2\) 。

于是证明了结论。

做法

有了上面的结论,我们可以知道,如果一条路径经过了一个点双连通分量,那么它一定可以取得这个点双连通分量中权值最小的点(绕过去)。由于一个点可以被很多点双连通分量包含(割点),但只能被一个父点双连通分量包含,所以我们考虑把这个图建成一棵树,每一个割点对删去它之后的每一个连通块建一个特殊节点,这个特殊节点连向这个连通块内的所有节点。这样每个特殊节点只有一个父亲(割点),所以它们构成了一棵树,点数为\(O(n)\) 。

一条路径的查询,如果它经过一个点双连通分量,那么必定可以取得其中的最小值,所以对每一个特殊点开一个multiset,记录这个点双连通分量中的最小点权,每次修改的时候修改当前节点的\(w\)值和父特殊点(正常点若有父亲一定是特殊点,特殊点的父亲一定是正常点)的multiset,用树链剖分和线段树维护一下路径最小值即可(也可以用LCT)。

如果查询的两点的lca为特殊点,那么其实它的父亲(割点)的信息是不在里面的,但也是可以走到的,所以特殊处理一下即可。

这题我写的时候在求点双连通分量的时候遇到了一点麻烦。边双连通分量可以先把桥求出来再跑一次dfs不走桥,点双连通分量则要把边压进栈,对于每一个未走过的点的出边,判断是否\(\text{low}[v]\ge \text{dfn}[v]\),若是的话就弹栈并把边中的点记录一下(或者操作一下什么的),知道遇到当前边。这是因为一个割点可能是很多个点双连通分量的割顶,所以直接用点来处理会出问题(少一些点),而边是不会遗漏的。每一条出边都是一个新的点双连通分量。

时间复杂度\(O(n\log ^2n)\)。

代码

#include<cstdio>
#include<cctype>
#include<cstring>
#include<set>
#include<algorithm>
using namespace std;
int read() {
int x=0,f=1;
char c=getchar();
for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
for (;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*f;
}
const int inf=1e9+7;
const int maxn=2e5+1;
int w[maxn],n,m,q,all,back[maxn];
multiset<int> st[maxn];
inline void Min(int &x,int y) {x=min(x,y);}
namespace sgt {
int t[maxn<<2];
void build(int x,int l,int r) {
if (l==r) {
int p=back[l];
t[x]=(p>n?*st[p].begin():w[p]);
return;
}
int mid=(l+r)>>1;
build(x<<1,l,mid),build(x<<1|1,mid+1,r);
t[x]=min(t[x<<1],t[x<<1|1]);
}
void modify(int x,int l,int r,int p,int d) {
if (l==r) {
t[x]=d;
return;
}
int mid=(l+r)>>1;
p<=mid?modify(x<<1,l,mid,p,d):modify(x<<1|1,mid+1,r,p,d);
t[x]=min(t[x<<1],t[x<<1|1]);
}
void modify(int p,int d) {
modify(1,1,all,p,d);
}
int query(int x,int L,int R,int l,int r) {
if (L==l && R==r) return t[x];
int mid=(L+R)>>1;
if (r<=mid) return query(x<<1,L,mid,l,r);
if (l>mid) return query(x<<1|1,mid+1,R,l,r);
return min(query(x<<1,L,mid,l,mid),query(x<<1|1,mid+1,R,mid+1,r));
}
int query(int l,int r) {
if (l>r) return inf;
return query(1,1,all,l,r);
}
}
namespace tree {
int dfx=0,first[maxn],second[maxn],top[maxn],size[maxn],son[maxn],dep[maxn],fat[maxn];
vector<int> g[maxn];
void add(int x,int y) {g[x].push_back(y);}
int dfs(int x,int fa) {
fat[x]=fa;
dep[x]=dep[fa]+1;
int &sz=size[x]=1,&sn=son[x]=0;
for (int v:g[x]) {
sz+=dfs(v,x);
if (size[v]>size[sn]) sn=v;
}
return sz;
}
void Top(int x,int tp) {
top[x]=tp;
back[first[x]=++dfx]=x;
if (son[x]) Top(son[x],tp);
for (int v:g[x]) if (v!=son[x]) Top(v,v);
second[x]=dfx;
}
int lca(int x,int y) {
for (;top[x]!=top[y];dep[top[x]]>dep[top[y]]?x=fat[top[x]]:y=fat[top[y]]);
return dep[x]<dep[y]?x:y;
}
void change(int x,int y) {
if (x!=1) {
int f=fat[x];
multiset<int> &mt=st[f];
mt.erase(mt.find(w[x]));
mt.insert(y);
sgt::modify(first[f],*mt.begin());
}
w[x]=y;
sgt::modify(first[x],y);
}
int query(int x,int y) {
if (x==y) return w[x];
int l=lca(x,y),ret=inf;
for (int i=top[x];dep[i]>=dep[l];i=top[x=fat[i]]) Min(ret,sgt::query(first[i],first[x]));
if (dep[x]>=dep[l]) Min(ret,sgt::query(first[l],first[x]));
for (int i=top[y];dep[i]>dep[l];i=top[y=fat[i]]) Min(ret,sgt::query(first[i],first[y]));
if (dep[y]>dep[l]) Min(ret,sgt::query(first[l]+1,first[y]));
if (l>n) Min(ret,w[fat[l]]);
return ret;
}
}
namespace graph {
int dfx=0,low[maxn],dfn[maxn],top=0,tic[maxn];
pair<int,int> sta[maxn];
bool ins[maxn];
vector<int> g[maxn];
void add(int x,int y) {g[x].push_back(y);}
void Tarjan(int x) {
ins[x]=true;
dfn[x]=low[x]=++dfx;
for (int v:g[x]) if (!dfn[v]) {
pair<int,int> b=sta[++top]=make_pair(x,v);
Tarjan(v);
if (low[v]>=dfn[x]) {
int spe=++all;
tree::add(x,spe);
while (true) {
pair<int,int> p=sta[top--];
int fir=p.first,sec=p.second;
if (fir!=x && tic[fir]!=dfn[x]) tic[fir]=dfn[x],tree::add(spe,fir),st[spe].insert(w[fir]);
if (sec!=x && tic[sec]!=dfn[x]) tic[sec]=dfn[x],tree::add(spe,sec),st[spe].insert(w[sec]);
if (p==b) break;
}
}
low[x]=min(low[x],low[v]);
} else if (ins[v]) low[x]=min(low[x],dfn[v]);
ins[x]=false;
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("test.in","r",stdin);
#endif
n=all=read(),m=read(),q=read();
for (int i=1;i<=n;++i) w[i]=read();
for (int i=1;i<=m;++i) {
int x=read(),y=read();
graph::add(x,y),graph::add(y,x);
}
graph::Tarjan(1);
tree::dfs(1,0);
tree::Top(1,1);
sgt::build(1,1,all);
while (q--) {
static char o[3];
scanf("%s",o);
int x=read(),y=read();
if (o[0]=='C') tree::change(x,y); else
if (o[0]=='A') {
int ans=tree::query(x,y);
printf("%d\n",ans);
}
}
return 0;
}

CF487E-Tourists的更多相关文章

  1. [UOJ30]/[CF487E]Tourists

    [UOJ30]/[CF487E]Tourists 题目大意: 一个\(n(n\le10^5)\)个点\(m(m\le10^5)\)条边的无向图,每个点有点权.\(q(q\le10^5)\)次操作,操作 ...

  2. 【学习笔记】圆方树(CF487E Tourists)

    终于学了圆方树啦~\(≧▽≦)/~ 感谢y_immortal学长的博客和帮助 把他的博客挂在这里~ 点我传送到巨佬的博客QwQ! 首先我们来介绍一下圆方树能干什么呢qwq 1.将图上问题简化到树上问题 ...

  3. CF487E Tourists(圆方树+树链剖分+multiset/可删堆)

    CF487E Tourists(圆方树+树链剖分+multiset/可删堆) Luogu 给出一个带点权的无向图,两种操作: 1.修改某点点权. 2.询问x到y之间简单路径能走过的点的最小点权. 题解 ...

  4. CF487E Tourists 【圆方树 + 树剖 + 堆】

    题目链接 CF487E 题解 圆方树 + 树剖 裸题 建好圆方树维护路径上最小值即可 方点的值为其儿子的最小值,这个用堆维护 为什么只维护儿子?因为这样修改点的时候就只需要修改其父亲的堆 这样充分利用 ...

  5. CF487E Tourists 题解

    题目链接 思路分析 看到这道题首先想到的此题的树上版本.(不就是树链剖分的板子题么?) 但是此题是图上的两点间的走法,自然要想到是圆方树. 我们先无脑构建出圆方树. 我们先猜测:设后加入的节点权值为 ...

  6. CF487E Tourists 圆方树、树链剖分

    传送门 注意到我们需要求的是两点之间所有简单路径中最小值的最小值,那么对于一个点双联通分量来说,如果要经过它,则一定会经过这个点双联通分量里权值最小的点 注意:这里不能缩边双联通分量,样例\(2\)就 ...

  7. CF487E Tourists - Tarjan缩点 + 树剖 + multiset

    Solution 先Tarjan求出点双联通分量 并缩点. 用$multiset$维护 点双内的最小点权. 容易发现, 点双内的最小点权必须包括与它相连的割边的点权. 所以我们必须想办法来维护. 所以 ...

  8. CF487E Tourists【圆方树+tarjan+multiset+树剖+线段树】

    圆方树不仅能解决仙人掌问题(虽然我仙人掌问题也没用过圆方树都是瞎搞过去的),还可以解决一般图的问题 一般图问题在于缩完环不是一棵树,所以就缩点双(包括双向边) 每个方点存他所在点双内除根以外的点的最小 ...

  9. CF487E Tourists(圆方树+堆+链剖)

    本题解并不提供圆方树讲解. 所以不会圆方树的出门右转问yyb 没有修改的话圆方树+链剖. 方点的权值为点双连通分量里的最小值. 然后修改的话圆点照修,每一个方点维护一个小根堆. 考虑到可能被菊花卡死. ...

  10. CF487E Tourists[圆方树+树剖(线段树套set)]

    做这题的时候有点怂..基本已经想到正解了..结果感觉做法有点假,还是看了正解题解.. 首先提到简单路径上经过的点,就想到了一个关于点双的结论:两点间简单路径上所有可能经过的点的并等于路径上所有点所在点 ...

随机推荐

  1. 20155315 实验一《Java开发环境的熟悉》实验报告

    实验一 Java开发环境的熟悉(Linux + IDEA) 实验内容 1.使用JDK编译.运行简单的Java程序: 2.使用IDEA编辑.编译.运行.调试Java程序. 实验要求 1.没有Linux基 ...

  2. C++ OI图论 学习笔记(初步完结)

    矩阵图 使用矩阵图来存储有向图和无向图的信息,用无穷大表示两点之间不连通,用两点之间的距离来表示连通.无向图的矩阵图是关于主对角线对称的. 如图所示: 使用dfs和bfs对矩阵图进行遍历 多源最短路径 ...

  3. STM32L431仿真卡在HAL_InitTick(TICK_INT_PRIORITY);

    1. 使用IAR 8.20版本,STM32L431RBT芯片,JLINK V9仿真器,实际仿真测试的时候卡在如下的函数 /* Use SysTick as time base source and c ...

  4. XAF-如何修改内置的编辑器(Property Editor)

    本示例演示在web/win中给 日期选择控制显示出一个时钟及修改时间的控件.效果如下: 如果你装了XAF在这个路径中已经有了这个示例: %PUBLIC%\Documents\DevExpress De ...

  5. Mysql取消SSH链接和恢复SSH链接

    取消SSH链接//键入密码,链接上mysql mysql -u root -p USE MYSQL; GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIF ...

  6. loadrunner之做压力测试要做的准备

    前提B/S架构 1.要有个备库和主库保存一致 到时候做压力测试的时候,要断开主库连接到备库.进行测试.以免主库出现垃圾数据.2.节点 判断单节点能承受多大的压力,如200万的用户账号,10万的在线用户 ...

  7. Ubuntu系统python3版本设置问题

    参照:https://blog.csdn.net/wangguchao/article/details/82151372

  8. linux-ubuntu常用命令(深圳文鹏)

    系统信息 arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 - (SMBIOS ...

  9. iOS 播放音频文件

    //        播放音乐 NSString *path = [[NSBundle mainBundle] pathForResource:@"1670" ofType:@&qu ...

  10. Java基础知识:Java实现Map集合二级联动4

    comboBox.setModel(new DefaultComboBoxModel(getProvince())); // 添加省份信息 final JLabel label = new JLabe ...