[Codeforces487E]Tourists(Tarjan+树链剖分+STL)

=== ===

这里放传送门

=== ===

题解

这题的意思是给一个无向图,求从某个点到某个点中间不经过重复点能够到达的所有点的点权最小值,还要支持修改某个点的点权。
因为不能重复到达点,所以当它在某个点的时候,和这个点属于同一个点双联通分量的点肯定全都能用来统计答案。而因为点双缩点以后是一棵树,所以如果能把每个点双缩点,点权设置为它里面所有点的最小值,那么直接跑链剖就可以了
然而把点双摘出来然后编号然后统计点权就是一个非常麻烦的工作。。因为割点同时属于多个点双联通分量,实现起来会有很多细节。。。

比较好写的做法是对每个点双新建一个点,然后每个点向它所属的点双连一条边。这样的话所有的割点会连到好几个点双上去,而非割点只会被连到一个点双上去。这样的话样例里面那个图搞出来就是这个样子:
[Codeforces487E]Tourists(Tarjan+树链剖分+STL)_第1张图片
可以看出这样建树以后树的模式一定是一个点连着一个块再连着一个点这样的。所以每个点的父亲一定是一个代表点双的块,并且这个点一定属于这个点双。那么用一个multiset来维护点双里面的最小值,把所有的点更新到它父亲节点上就可以了。
查询的时候可以直接链剖,但有一个问题就是如果两个点的LCA是一个块,那么这个块的father应该也是一个属于这个块的割点,要把这个点特判一下。
修改的时候除了修改当前点以外还要在multiset里维护一下。

代码

#include
#include
#include
#include
using namespace std;
int n,m,q,val[100010],cnt,tot,p[100010],a[200010],nxt[200010],w[200010],low[200010];
int top,st[200010],rec,point,father[200010];
bool mak[200010];
multiset<int> s[100010];
void add(int x,int y){
    tot++;a[tot]=y;nxt[tot]=p[x];p[x]=tot;
}
int rev(int i){return (i&1)?(i+1):(i-1);}
int find(int x){
    if (father[x]==x) return father[x];
    father[x]=find(father[x]);
    return father[x];
}
void merge(int x,int y){
    int r1,r2;
    r1=find(x);r2=find(y);
    if (r1!=r2) father[r1]=r2;
}
namespace Tree{
    int p[200010],a[400010],nxt[400010],top[200010],size[200010],son[200010],fa[200010];
    int w[200010],cnt,tot,num[200010],Min[800010],deep[200010],val[200010],N;
    multiset<int>::iterator it;
    void add(int x,int y){
        tot++;a[tot]=y;nxt[tot]=p[x];p[x]=tot;
    }
    void dfs(int u){
        deep[u]=deep[fa[u]]+1;
        size[u]=1;son[u]=0;
        for (int i=p[u];i!=0;i=nxt[i])
          if (a[i]!=fa[u]){
              fa[a[i]]=u;dfs(a[i]);
              size[u]+=size[a[i]];
              if (size[a[i]]>size[son[u]])
                son[u]=a[i];
          }
    }
    void dfs_again(int u,int tp){
        top[u]=tp;w[u]=++cnt;num[cnt]=u;
        if (son[u]!=0) dfs_again(son[u],tp);
        for (int i=p[u];i!=0;i=nxt[i])
          if (a[i]!=fa[u]&&a[i]!=son[u])
            dfs_again(a[i],a[i]);
    }
    void update(int i){Min[i]=min(Min[i<<1],Min[(i<<1)+1]);}
    void build(int i,int l,int r){
        if (l==r){Min[i]=val[num[l]];return;}
        int mid=(l+r)>>1;
        build(i<<1,l,mid);
        build((i<<1)+1,mid+1,r);
        update(i);
    }
    void BuildTree(int point){
        N=point;
        dfs(1);dfs_again(1,1);
        for (int i=1;i<=n;i++)
          if (fa[i]>n)//把当前点的值加到它父亲上面去
            s[fa[i]-n].insert(val[i]);
        for (int i=n+1;i<=N;i++){
            it=s[i-n].begin();//找到集合中的最小元素
            val[i]=(*it);
        }
        build(1,1,N);
    }
    int ask(int i,int l,int r,int left,int right){
        if (left<=l&&right>=r) return Min[i];
        int mid=(l+r)>>1,ans=0x7fffffff;
        if (left<=mid) ans=min(ans,ask(i<<1,l,mid,left,right));
        if (right>mid) ans=min(ans,ask((i<<1)+1,mid+1,r,left,right));
        return ans;
    }
    void change(int i,int l,int r,int x,int v){
        if (l==r){Min[i]=v;return;}
        int mid=(l+r)>>1;
        if (x<=mid) change(i<<1,l,mid,x,v);
        else change((i<<1)+1,mid+1,r,x,v);
        update(i);
    }
    int Query(int x,int y){
        int ans=0x7fffffff;
        while (top[x]!=top[y]){
            if (deep[top[x]]1,1,N,w[top[x]],w[x]));
            x=fa[top[x]];
        }
        if (deep[x]>deep[y]) swap(x,y);
        ans=min(ans,ask(1,1,N,w[x],w[y]));
        if (x>n) ans=min(ans,val[fa[x]]);
        return ans;
    }
    void Change(int x,int y){
        int pos=fa[x]-n;
        if (pos>0){//注意只有上面还有点的时候才修改
            it=s[pos].find(val[x]);
            s[pos].erase(it);//删除迭代器位置的值
            s[pos].insert(y);
            it=s[pos].begin();
            val[fa[x]]=(*it);
            change(1,1,N,w[fa[x]],val[fa[x]]);
        }
        change(1,1,N,w[x],y);val[x]=y;
    }
    void print(int N){
        for (int i=1;i<=N;i++)
          for (int j=p[i];j!=0;j=nxt[j])
            printf("%d %d\n",i,a[j]);
    }
}
void Tarjan(int u,int fa,int rt){
    w[u]=low[u]=++cnt;st[++top]=u;
    for (int i=p[u];i!=0;i=nxt[i])
      if (w[a[i]]==0){
          if (u==1) ++rec;
          Tarjan(a[i],u,rt);
          low[u]=min(low[u],low[a[i]]);
          if (low[a[i]]>=w[u]){
              int v;mak[u]=true;
              ++point;
              do{//求出每一个点双连通分量
                 v=st[top--];
                 Tree::add(v,point);
                 Tree::add(point,v);
                 merge(point,v);     
              }while (v!=a[i]);
              Tree::add(u,point);
              Tree::add(point,u);
              merge(point,u);
          }
      }else if (a[i]!=fa) low[u]=min(low[u],w[a[i]]);
}
int main()
{
    scanf("%d%d%d",&n,&m,&q);
    for (int i=1;i<=n;i++) scanf("%d",&val[i]);
    for (int i=1;i<=m;i++){
        int x,y;scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    point=n;Tarjan(1,0,1);
    for (int i=1;i<=n;i++)
      if (mak[i]==true)
        for (int j=p[i];j!=0;j=nxt[j])
          if (mak[a[j]]==true&&find(i)!=find(a[j])){
              ++point;
              Tree::add(point,i);Tree::add(i,point);
              Tree::add(point,a[j]);Tree::add(a[j],point);
          }
    for (int i=1;i<=n;i++)
      Tree::val[i]=val[i];
    Tree::BuildTree(point);
    for (int i=1;i<=q;i++){
        char c=getchar();
        int x,y;
        while (c!='A'&&c!='C') c=getchar();
        scanf("%d%d",&x,&y);
        if (c=='A') printf("%d\n",Tree::Query(x,y));
        else Tree::Change(x,y);
    }
    return 0;
}

偏偏在最后出现的补充说明

对于处理点双联通分量的问题,由于割点同时属于多个点双,这样的建树模式可以简化对于割点的处理,所以好像是很常见的方法

你可能感兴趣的:(Codeforces,杂七杂八的图论,不知道加什么形容词的树链剖分,STL大法好)