[点双连通分量 缩点 树链剖分] Codeforces 487E #278 (Div. 1) E. Tourists

点双连通分量的缩点技巧

来自: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;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值