点双连通分量的缩点技巧
来自:http://blog.csdn.net/a_crazy_czy/article/details/52244069
在这里我们使用这样一种方法:对于每一个点双连通分量,我们建一个新建点储存点双所有点的最小权值,然后该点向点双内所有
不是深度最小的点
连一条边,然后深度最小的点(大多情况为割点,当然树根不一定是割点)向这个新建点连边。
注意到这棵树一定是普通点连向新建点连向普通点这样交替。修改的时候我们修改这个点自身的权值,还要把这个点所属的新建点(父亲节点)修改了。查询的时候呢?首先先查询两点之间路径的最小值,然后如果两点
LCA
是新建点,那就还要查询它的父亲的最小值更新。
为什么这样是对的呢?因为查询的时候,除了最顶端
LCA
所在的点双,其它点的点双的顶点都是肯定能够经过的,查询是不会出错的,最顶端的
LCA
如果是一个新建点,那么就证明我还是可以通过该点双的顶点的,但是我没有用这个顶点更新过点双,因此要再用这个顶点(即
LCA
父亲)更新一下答案。否则这是一个顶点,如果我要经过该点双其它位置,那么必然要经过顶点两次,那是不行的,因此不用管。
关于怎么维护点双内最小值,对每一个新建点开一个
multiset
就好了。
大概是这样 灵魂画作
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<stack>
#include<set>
#include<cstring>
#define cl(x) memset(x,0,sizeof(x))
using namespace std;
inline char nc(){
static char buf[100000],*p1=buf,*p2=buf;
if (p1==p2) { p2=(p1=buf)+fread(buf,1,100000,stdin); if (p1==p2) return EOF; }
return *p1++;
}
inline void read(int &x){
char c=nc(),b=1;
for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}
inline void read(char &x){
for (x=nc();x!='A' && x!='C';x=nc());
}
const int N=200005;
struct SEG{
int T[N<<2],M;
inline void Build(int n){
for (M=1;M<n+2;M<<=1);
for (int i=2*M-1;i;i--)
T[i]=1<<30;
}
inline void Modify(int s,int r){
T[s+=M]=r;
while (s>>=1)
T[s]=min(T[s<<1],T[s<<1|1]);
}
inline int Query(int s,int t){
int ret=1<<30;
for (s+=M-1,t+=M+1;s^t^1;s>>=1,t>>=1){
if (~s&1) ret=min(ret,T[s^1]);
if ( t&1) ret=min(ret,T[t^1]);
}
return ret;
}
}Seg;
struct edge{
int u,v,next;
}G[N<<4];
int head1[N],head2[N],inum=1;
int *head;
inline void add(int u,int v,int p,int *head=::head){
G[p].u=u; G[p].v=v; G[p].next=head[u]; head[u]=p;
}
stack<int> S;
multiset<int> Set[100005];
int pre[N],low[N],clk;
#define V G[p].v
int bcc;
int n,m,val[N];
inline void Tarjan(int u,int fa){
pre[u]=low[u]=++clk; S.push(u);
for (int p=head[u];p;p=G[p].next)
if (!pre[V]){
Tarjan(V,p);
low[u]=min(low[u],low[V]);
if (low[V]>=pre[u]){
bcc++;
add(u,n+bcc,++inum,head2),add(n+bcc,u,++inum,head2);
while (1){
int x=S.top(); S.pop();
add(n+bcc,x,++inum,head2),add(x,n+bcc,++inum,head2);
Set[bcc].insert(val[x]);
if (x==V) break;
}
}
}else if (p!=(fa^1))
low[u]=min(low[u],pre[V]);
}
int depth[N],size[N],fat[N];
int tid[N],top[N];
inline void dfs(int u,int fa){
depth[u]=depth[fa]+1; size[u]=1; fat[u]=fa;
for (int p=head[u];p;p=G[p].next)
if (V!=fa)
dfs(V,u),size[u]+=size[V];
}
inline void find(int u,int fa,int z){
tid[u]=++clk; top[u]=z;
int son=0,maxv=0;
for (int p=head[u];p;p=G[p].next)
if (V!=fa && size[V]>maxv)
maxv=size[son=V];
if (son) find(son,u,z);
for (int p=head[u];p;p=G[p].next)
if (V!=fa && V!=son)
find(V,u,V);
}
inline int Query(int u,int v){
int ret=1<<30;
for (;top[u]!=top[v];u=fat[top[u]]){
if (depth[top[u]]<depth[top[v]]) swap(u,v);
ret=min(ret,Seg.Query(tid[top[u]],tid[u]));
}
if (depth[u]>depth[v]) swap(u,v);
ret=min(ret,Seg.Query(tid[u],tid[v]));
int lca=u;
if (lca>n)
ret=min(ret,val[fat[lca]]);
return ret;
}
int main(){
int Q,iu,iv; char order;
freopen("t.in","r",stdin);
freopen("t.out","w",stdout);
read(n); read(m); read(Q);
for (int i=1;i<=n;i++) read(val[i]);
head=head1;
for (int i=1;i<=m;i++)
read(iu),read(iv),add(iu,iv,++inum),add(iv,iu,++inum);
Tarjan(1,0);
head=head2;
clk=0; dfs(1,0); find(1,0,1);
for (int i=1;i<=bcc;i++) val[n+i]=*Set[i].begin();
Seg.Build(n+bcc);
for (int i=1;i<=n+bcc;i++) Seg.Modify(tid[i],val[i]);
while (Q--){
read(order); read(iu); read(iv);
if (order=='A')
printf("%d\n",Query(iu,iv));
else{
if (fat[iu]){
int fa=fat[iu];
Set[fa-n].erase(Set[fa-n].find(val[iu]));
Set[fa-n].insert(iv);
val[fa]=*Set[fa-n].begin();
Seg.Modify(tid[fa],val[fa]);
}
val[iu]=iv;
Seg.Modify(tid[iu],val[iu]);
}
}
return 0;
}