题解 CF487E 【Tourists】
【CF487E】Tourists 题解
之前WC的时候太蠢了,没听懂圆方树,一直感觉是神仙东西,直到做了这道题,才对圆方树稍有了一些感觉
圆方树是这样的:对于一个无向连通图,求出它的所有点-双连通分量,然后对于每个点双新建一个点,称之为“方点”,然后原图的点称之为“圆点”,每个圆点向它所在点双对应的方点连边(注意:一个点可能属于多个点双,因此一个圆点需要向多个方点连边),建出一个新的图。这个新图满足:它是一棵树,它最多只有2n-1个点(n是原图点数),所有的圆点只与方点相邻、所有的方点只与圆点相邻(“相邻”定义为某两个点有一条边直接连接)
这个题和圆方树有什么关系呢?
一个非常显然的事实就是:当你到达一个点双时,一定存在一条简单路径,从外部进入这个点双,然后经过点双里面的权值最小点,然后再走出这个点双。所以一个点双对答案的贡献必然是点双里面的最小权值
于是我们可以建立圆方树,方点的权值为点双中的最小圆点权值。然后原图就变成了一棵树,询问时就可以直接树剖套线段树求路径最小值了
但是修改操作似乎并不是非常方便。因为一个圆点的权值变动,可能会引发与之相连的方点权值变动(当这个圆点是点双中的的最小权值点时会发生这件事情)。那么我们可以对每个方点维护一个multiset,里面存所有与之相邻的圆点权值,然后权值就是multiset中的最小值,每次修改就删掉原来的权值,插入新的权值。然后我们每修改一个圆点的权值,我们就遍历与之相邻的所有方点并按上述方法修改multiset
但这样是会被菊花图卡成n方的。因为菊花图的根节点,有n-1个方点与之相邻,每次修改都遍历一遍的话,就GG了
于是一位巨佬告诉了我更加优秀的方法:对于一个方点,multiset里面存它所有子节点的权值。然后修改一个圆点时,就只需要动它父亲的multiset(它的父亲必然是一个方点)。询问时,仍然可以正常询问,只不过如果lca是一个方点,那还要额外计算fa[lca]的权值对答案的贡献
具体实现需要用Tarjan求一下点双,然后还要一棵维护区间最小值的线段树,以及STL的multiset
#include<bits/stdc++.h>
#define INF 0x7f7f7f7f
using namespace std;
namespace IO
{
const int S=(1<<20)+5;
//Input Correlation
char buf[S],*H,*T;
inline char Get()
{
if(H==T) T=(H=buf)+fread(buf,1,S,stdin);
if(H==T) return -1;return *H++;
}
inline int read()
{
int x=0;char c=Get();
while(!isdigit(c)) c=Get();
while(isdigit(c)) x=x*10+c-'0',c=Get();
return x;
}
//Output Correlation
char obuf[S],*oS=obuf,*oT=oS+S-1,c,qu[55];int qr;
inline void flush(){fwrite(obuf,1,oS-obuf,stdout);oS=obuf;}
inline void putc(char x){*oS++ =x;if(oS==oT) flush();}
template <class I>inline void print(I x)
{
if(!x) putc('0');
if(x<0) putc('-'),x=-x;
while(x) qu[++qr]=x%10+'0',x/=10;
while(qr) putc(qu[qr--]);
putc('\n');
}
}
using namespace IO;
inline void upmin(int &x,const int &y){if(y<x) x=y;}
const int N=100010;
int val[N<<1],n,m,q,tot;
multiset<int> st[N];
namespace TCD
{
const int N=200010;
struct Edge{int to,next;} e[N<<1];
int h[N],sum=0;
int fa[N],top[N],hson[N];
int size[N],dep[N];
int dfn[N],idx[N],clk=0;
int mn[N<<2];
void AddEdge(int u,int v)
{
e[++sum].to=v;
e[sum].next=h[u];
h[u]=sum;
}
void add_edge(int u,int v)
{
AddEdge(u,v);
AddEdge(v,u);
}
void dfs1(int u,int la)
{
size[u]=1;int mx=0;
for(int tmp=h[u];tmp;tmp=e[tmp].next)
{
int v=e[tmp].to;
if(v==la) continue;
dep[v]=dep[u]+1;
dfs1(v,u);fa[v]=u;
size[u]+=size[v];
if(size[v]>mx) mx=size[v],hson[u]=v;
}
}
void dfs2(int u,int tp)
{
top[u]=tp;idx[dfn[u]=++clk]=u;
if(hson[u]) dfs2(hson[u],tp);
for(int tmp=h[u];tmp;tmp=e[tmp].next)
if(e[tmp].to!=fa[u]&&e[tmp].to!=hson[u])
dfs2(e[tmp].to,e[tmp].to);
}
inline void maintain(int o){mn[o]=min(mn[o<<1],mn[o<<1|1]);}
void Build(int o,int l,int r)
{
if(l==r){mn[o]=val[idx[l]];return;}
int mid=(l+r)/2;
Build(o<<1,l,mid);
Build(o<<1|1,mid+1,r);
maintain(o);
}
void Modify(int o,int l,int r,int k,int x)
{
if(l==r){mn[o]=x;return;}
int mid=(l+r)/2;
if(k<=mid) Modify(o<<1,l,mid,k,x);
else Modify(o<<1|1,mid+1,r,k,x);
maintain(o);
}
int Query(int o,int l,int r,int nl,int nr)
{
if(l>=nl&&r<=nr) return mn[o];
int mid=(l+r)/2,res=INF;
if(nl<=mid) upmin(res,Query(o<<1,l,mid,nl,nr));
if(nr>mid) upmin(res,Query(o<<1|1,mid+1,r,nl,nr));
return res;
}
int PathQuery(int u,int v)
{
int res=INF;
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
upmin(res,Query(1,1,tot,dfn[top[u]],dfn[u]));
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
upmin(res,Query(1,1,tot,dfn[u],dfn[v]));
if(u>n) upmin(res,val[fa[u]]);
return res;
}
}
namespace Graph
{
struct Edge{int to,next;} e[N<<1];
int h[N],sum=0;
int pre[N],low[N],dfn=0;
int bcc[N];
stack<int> stk;
void add_edge(int u,int v)
{
e[++sum].to=v;
e[sum].next=h[u];
h[u]=sum;
}
void Tarjan(int u)
{
pre[u]=low[u]=++dfn;stk.push(u);
for(int tmp=h[u];tmp;tmp=e[tmp].next)
{
int v=e[tmp].to;
if(!pre[v])
{
Tarjan(v);
upmin(low[u],low[v]);
if(low[v]>=pre[u])
{
int o;tot++;
do{
o=stk.top();
stk.pop();
bcc[o]=tot;
TCD::add_edge(o,tot);
}while(o!=v);
TCD::add_edge(u,tot);
}
}
else upmin(low[u],pre[v]);
}
}
}
int main()
{
int u,v;
tot=n=read();m=read();q=read();
for(int i=1;i<=n;i++) val[i]=read();
for(int i=1;i<=m;i++)
{
u=read();v=read();
Graph::add_edge(u,v);
Graph::add_edge(v,u);
}
for(int i=1;i<=n;i++)
if(!Graph::pre[i]) Graph::Tarjan(i);
TCD::dfs1(1,0);
TCD::dfs2(1,1);
for(int i=2;i<=n;i++)
st[TCD::fa[i]-n].insert(val[i]);
for(int i=n+1;i<=tot;i++)
val[i]=st[i-n].empty()?INF:*st[i-n].begin();
TCD::Build(1,1,tot);
while(q--)
{
char opt=Get();
while(opt!='C'&&opt!='A') opt=Get();
u=read();v=read();
if(opt=='C')
{
TCD::Modify(1,1,tot,TCD::dfn[u],v);
if(u==1){val[u]=v;continue;}
int o=TCD::fa[u];
st[o-n].erase(st[o-n].find(val[u]));
st[o-n].insert(v);
int minv=*st[o-n].begin();
if(minv==val[o]){val[u]=v;continue;}
TCD::Modify(1,1,tot,TCD::dfn[o],minv);
val[o]=minv;val[u]=v;
}
else print(TCD::PathQuery(u,v));
}
flush();
return 0;
}