UOJ#30/Codeforces 487E Tourists 点双连通分量,Tarjan,圆方树,树链剖分,线段树
Posted zhouzhendong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UOJ#30/Codeforces 487E Tourists 点双连通分量,Tarjan,圆方树,树链剖分,线段树相关的知识,希望对你有一定的参考价值。
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ30.html
题目传送门 - UOJ#30
题意
uoj写的很简洁、清晰,这里就不抄一遍了。
题解
首先建出圆方树。接下来,我们称"圆点"为原来有的点,"方点"为新增的点。
然后先只考虑在线询问如何做。
——把方点的值设置成所有与他连边的圆点的权值的最小值,直接在圆方树上树链剖分再套个线段树支持一下区间询问即可。
然后会发现这样做支持不了修改操作。
——直接来个菊花图不断修改根节点就GG了。
于是我们考虑进一步想办法。
我们把方点的值重新定义成“在圆方树上,该点的儿子的权值的最小值”。那么,在询问的时候,其他都一样,但是如果 lca 为方点,那么加上其 fa 对 min 的贡献即可。
时间复杂度 $O(nlog ^2 n )$ 。
代码
#include <bits/stdc++.h> using namespace std; typedef long long LL; LL read(){ LL x=0,f=1; char ch=getchar(); while (!isdigit(ch)&&ch!=‘-‘) ch=getchar(); if (ch==‘-‘) f=-1,ch=getchar(); while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); return x*f; } const int N=200005,INF=1.1e9; int pr[N],dfn[N],low[N],st[N],Time,top,tot; multiset <int> Mins[N]; vector <int> G[N],g[N]; void Tarjan(int x){ low[x]=dfn[x]=++Time,st[++top]=x; for (auto y : G[x]) if (!dfn[y]){ Tarjan(y); low[x]=min(low[x],low[y]); if (low[y]>=dfn[x]){ tot++; g[x].push_back(tot); g[tot].push_back(x); int z; do { z=st[top--]; g[z].push_back(tot); g[tot].push_back(z); } while (z!=y); } } else low[x]=min(low[x],dfn[y]); } namespace sp{ int n,outn; int fa[N],son[N],size[N],depth[N],top[N],p[N],ap[N],cnp=0,val[N]; void dfs(int x,int pre,int d){ size[x]=1,fa[x]=pre,son[x]=-1,depth[x]=d; for (auto y : g[x]) if (y!=pre){ dfs(y,x,d+1); size[x]+=size[y]; if (son[x]==-1||size[y]>size[son[x]]) son[x]=y; } } void Get_Top(int x,int tp){ top[x]=tp; ap[p[x]=++cnp]=x; if (son[x]==-1) return; Get_Top(son[x],tp); for (auto y : g[x]) if (y!=fa[x]&&y!=son[x]) Get_Top(y,y); } int Min[N<<2]; void build(int rt,int L,int R){ if (L==R) return (void)(Min[rt]=val[ap[L]]); int mid=(L+R)>>1,ls=rt<<1,rs=ls|1; build(ls,L,mid); build(rs,mid+1,R); Min[rt]=min(Min[ls],Min[rs]); } int query(int rt,int L,int R,int xL,int xR){ if (xL>xR||L>xR||R<xL) return INF; if (xL<=L&&R<=xR) return Min[rt]; int mid=(L+R)>>1,ls=rt<<1,rs=ls|1; return min(query(ls,L,mid,xL,xR),query(rs,mid+1,R,xL,xR)); } void update(int rt,int L,int R,int x,int d){ if (L==R) return (void)(Min[rt]=d); int mid=(L+R)>>1,ls=rt<<1,rs=ls|1; if (x<=mid) update(ls,L,mid,x,d); else update(rs,mid+1,R,x,d); Min[rt]=min(Min[ls],Min[rs]); } int query(int a,int b){ int f1=top[a],f2=top[b],ans=INF; while (f1!=f2){ if (depth[f1]<depth[f2]) swap(f1,f2),swap(a,b); ans=min(ans,query(1,1,n,p[f1],p[a])); a=fa[f1],f1=top[a]; } if (depth[a]>depth[b]) swap(a,b); if (a>outn&&fa[a]!=0) ans=min(ans,query(1,1,n,p[fa[a]],p[fa[a]])); return min(ans,query(1,1,n,p[a],p[b])); } } int main(){ int n=tot=read(),m=read(),q=read(); for (int i=1;i<=n;i++) pr[i]=read(); for (int i=1;i<=m;i++){ int a=read(),b=read(); G[a].push_back(b); G[b].push_back(a); } Tarjan(1); sp :: dfs(1,0,0); sp :: Get_Top(1,1); for (int i=n+1;i<=tot;i++) Mins[i].insert(INF); for (int i=1;i<=n;i++) Mins[sp :: fa[i]].insert(pr[i]); for (int i=1;i<=n;i++) sp :: val[i]=pr[i]; for (int i=n+1;i<=tot;i++) sp :: val[i]=*Mins[i].begin(); sp :: build(1,1,sp :: n=tot); sp :: outn=n; while (q--){ char s[10]; scanf("%s",s); int x=read(),y=read(); if (s[0]==‘A‘) printf("%d ",sp :: query(x,y)); else { int f=sp :: fa[x]; if (f){ Mins[f].erase(Mins[f].find(pr[x])); Mins[f].insert(pr[x]=y); sp :: update(1,1,sp :: n,sp :: p[f],*Mins[f].begin()); } sp :: update(1,1,sp :: n,sp :: p[x],y); } } return 0; }
以上是关于UOJ#30/Codeforces 487E Tourists 点双连通分量,Tarjan,圆方树,树链剖分,线段树的主要内容,如果未能解决你的问题,请参考以下文章