[Codeforces 487E]Tourists/[JZOJ4691]旅行/[UOJ#30]Tourists

题目大意

给定一个有 n 个点m条边的无向图,每个点有点权 wi
q 个操作,每次询问所有点x到点 y 的简单路径上最小点权是多少或者将点x权值改为 y
1n,m,q1×105,1wi109


题目分析

第一眼看见这题应该有个很显然的想法,就是点双连通分量缩点然后树链剖分。
但是实际想一想,细节还是很多的,怎样构图才能保证正确性而且修改的时候不会T?
在这里我们使用这样一种方法:对于每一个点双连通分量,我们建一个新建点储存点双所有点的最小权值,然后该点向点双内所有 连一条边,然后深度最小的点(大多情况为割点,当然树根不一定是割点)向这个新建点连边。
注意到这棵树一定是普通点连向新建点连向普通点这样交替。修改的时候我们修改这个点自身的权值,还要把这个点所属的新建点(父亲节点)修改了。查询的时候呢?首先先查询两点之间路径的最小值,然后如果两点 LCA 是新建点,那就还要查询它的父亲的最小值更新。
为什么这样是对的呢?因为查询的时候,除了最顶端 LCA 所在的点双,其它点的点双的顶点都是肯定能够经过的,查询是不会出错的,最顶端的 LCA 如果是一个新建点,那么就证明我还是可以通过该点双的顶点的,但是我没有用这个顶点更新过点双,因此要再用这个顶点(即 LCA 父亲)更新一下答案。否则这是一个顶点,如果我要经过该点双其它位置,那么必然要经过顶点两次,那是不行的,因此不用管。
关于怎么维护点双内最小值,对每一个新建点开一个 multiset 就好了。
时间复杂度 O(nlog22n)


代码实现

#include <algorithm>
#include <iostream>
#include <climits>
#include <cstring>
#include <cstdio>
#include <cctype>
#include <cmath>
#include <stack>
#include <set>

using namespace std;

typedef multiset<int>::iterator ptr;

const int INF=INT_MAX;
const int N=100050;
const int V=N<<1;
const int M=N<<1;
const int E=M<<1;
const int EL=V<<1;
const int LGEL=19;

multiset<int> bst[V];

struct G
{
    int next[E],tov[E];
    int last[V];
    int tot;

    void insert(int x,int y){tov[++tot]=y,next[tot]=last[x],last[x]=tot;}
}g,t;

int size[V],hea[V],prt[V],fa[V],DFN[V],LOW[N],val[V],high[V],pos[V];
int n,m,q,all,idx,el,lgel;
int rmq[EL][LGEL];
int euler[EL];
stack<int> S;

struct segment_tree
{
    int v[V<<2];

    void update(int x){v[x]=min(v[x<<1],v[x<<1|1]);}

    void modify(int x,int y,int l,int r,int edit)
    {
        if (l==r)
        {
            v[x]=edit;
            return;
        }
        int mid=l+r>>1;
        if (y<=mid) modify(x<<1,y,l,mid,edit);
        else modify(x<<1|1,y,mid+1,r,edit);
        update(x);
    }

    int query(int x,int st,int en,int l,int r)
    {
        if (st==l&&en==r) return v[x];
        int mid=l+r>>1;
        if (en<=mid) return query(x<<1,st,en,l,mid);
        else if (mid+1<=st) return query(x<<1|1,st,en,mid+1,r);
        else return min(query(x<<1,st,mid,l,mid),query(x<<1|1,mid+1,en,mid+1,r));
    }
}T;

void tarjan(int x,int fr)
{
    DFN[x]=LOW[x]=++idx,S.push(x);
    for (int i=g.last[x],y;i;i=g.next[i])
        if (!DFN[y=g.tov[i]])
        {
            tarjan(y,x);
            LOW[x]=min(LOW[x],LOW[y]);
            if (LOW[y]>=DFN[x])
            {
                int sp=++all;
                t.insert(x,sp);
                for (int u;;)
                {
                    u=S.top(),S.pop(),t.insert(sp,u),bst[sp].insert(val[u]);
                    if (u==y) break;
                }
            }
        }
        else if (y!=fr) LOW[x]=min(LOW[x],DFN[y]);
}

void dfs(int x)
{
    size[x]=1,hea[x]=0,rmq[pos[euler[++el]=x]=el][0]=x;
    for (int i=t.last[x],y;i;i=t.next[i])
    {
        high[y=t.tov[i]]=high[x]+1,fa[y]=x,dfs(y),euler[++el]=x,rmq[el][0]=x,size[x]+=size[y];
        if (!hea[x]||size[hea[x]]<size[y]) hea[x]=y;
    }
}

void build(int x,int fr)
{
    DFN[x]=++idx,prt[x]=fr;
    if (!hea[x]) return;
    build(hea[x],fr);
    for (int i=t.last[x],y;i;i=t.next[i])
        if ((y=t.tov[i])!=hea[x]) build(y,y);
}

void pre()
{
    lgel=trunc(log(el)/log(2));
    for (int j=1;j<=lgel;j++)
        for (int i=1;i+(1<<j)-1<=el;i++)
            rmq[i][j]=high[rmq[i][j-1]]<high[rmq[i+(1<<j-1)][j-1]]?rmq[i][j-1]:rmq[i+(1<<j-1)][j-1];
    for (int i=n+1;i<=all;i++) val[i]=*bst[i].begin();
    for (int i=1;i<=all;i++) T.modify(1,DFN[i],1,idx,val[i]);
}

int getrmq(int l,int r)
{
    int lgr=trunc(log(r-l+1)/log(2));
    return high[rmq[l][lgr]]<high[rmq[r-(1<<lgr)+1][lgr]]?rmq[l][lgr]:rmq[r-(1<<lgr)+1][lgr];
}

int lca(int x,int y)
{
    if ((x=pos[x])>(y=pos[y])) swap(x,y);
    return getrmq(x,y);
}

int getans(int x,int y)
{
    int z=lca(x,y),ret=INF;
    for (int u=prt[x];prt[x]!=prt[z];x=fa[u],u=prt[x]) ret=min(ret,T.query(1,DFN[u],DFN[x],1,idx));
    ret=min(ret,T.query(1,DFN[z],DFN[x],1,idx));
    for (int u=prt[y];prt[y]!=prt[z];y=fa[u],u=prt[y]) ret=min(ret,T.query(1,DFN[u],DFN[y],1,idx));
    ret=min(ret,T.query(1,DFN[z],DFN[y],1,idx));
    if (z>n) ret=min(ret,val[fa[z]]);
    return ret;
}

int main()
{
    freopen("tourists.in","r",stdin),freopen("tourists.out","w",stdout);
    scanf("%d%d%d",&n,&m,&q);
    for (int i=1;i<=n;i++) scanf("%d",&val[i]);
    for (int i=1,u,v;i<=m;i++)
    {
        scanf("%d%d",&u,&v);
        g.insert(u,v),g.insert(v,u);
    }
    all=n,tarjan(1,0),high[1]=1,fa[1]=0,dfs(1),idx=0,memset(DFN,0,sizeof DFN),build(1,1),pre();
    for (int i=1,a,b;i<=q;i++)
    {
        char opt=getchar();
        while (!isalpha(opt)) opt=getchar();
        scanf("%d%d",&a,&b);
        if (opt=='C')
        {
            if (fa[a]) bst[fa[a]].erase(bst[fa[a]].find(val[a]));
            val[a]=b,T.modify(1,DFN[a],1,idx,val[a]);
            if (fa[a]) bst[fa[a]].insert(val[a]),val[fa[a]]=*bst[fa[a]].begin(),T.modify(1,DFN[fa[a]],1,idx,val[fa[a]]);
        }
        else printf("%d\n",getans(a,b));
    }
    fclose(stdin),fclose(stdout);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值