Loading

OTOCI 题解

OTOCI

题目大意

给定 \(n\) 个带权的点,需要进行四种操作:查询两点连通性;加边;修改点权;查询两点路径的权值和。

思路分析

首先观察题目,我们会发现,在所有的操作结束后,所有的点构成一个森林,这是因为题目中的加边是建立在两点不连通的基础上的,所以不会形成任何的环,到最后自然形成了一个森林。

那么,我们可以不用理会题目的“要求在线处理所有操作”,使用离线大法跑树链剖分来解决了。

具体的说,我们先将所有的询问存下来离线处理,先用并查集维护两点的连通性和加边操作,然后在最后形成的森林上跑树剖,再依次处理每个询问。

时间复杂度:\(O(n\log^2n)\)

代码

#include <bits/stdc++.h>
using namespace std;
const int N=100100;//双向边

int to[N],nxt[N],head[N],w[N];
int fat[N],son[N],dep[N],siz[N],top[N],dfn[N],rnk[N];//树剖七件套
int idx,n,m,in1,in2,q,cnt;
int fa[N],inp[N*3][3];char op[15];//并查集,询问

int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}//常规并查集
void add(int u,int v){idx++;to[idx]=v;nxt[idx]=head[u];head[u]=idx;}
void Swap(int &x,int &y){int t=x;x=y;y=t;}

struct STn{int l,r,sum;};//线段树求区间和
struct ST{
    STn a[N<<2];
    void build(int p,int l,int r){
        a[p].l=l;a[p].r=r;
        if(a[p].l==a[p].r){a[p].sum=w[rnk[a[p].l]];return ;}
        int mid=(a[p].l+a[p].r)>>1;
        build(p<<1,l,mid);build(p<<1|1,mid+1,r);
        a[p].sum=a[p<<1].sum+a[p<<1|1].sum;return ;
    }
    void change(int p,int x,int k){//单点修改
        if(a[p].l==a[p].r){a[p].sum=k;return ;}
        int mid=(a[p].l+a[p].r)>>1;
        if(x<=mid) change(p<<1,x,k);else change(p<<1|1,x,k);
        a[p].sum=a[p<<1].sum+a[p<<1|1].sum;return ;
    }
    int ask(int p,int l,int r){//求和
        if(l<=a[p].l&&a[p].r<=r) return a[p].sum;
        int mid=(a[p].l+a[p].r)>>1;
        return ((l<=mid)?ask(p<<1,l,r):0)+((r>mid)?ask(p<<1|1,l,r):0);
    }
}tree;

void dfs_1(int s,int gr){//常规树剖dfs
    dep[s]=dep[gr]+1;fat[s]=gr;
    son[s]=-1;siz[s]=1;
    for(int i=head[s];i;i=nxt[i]){
        int v=to[i];
        if(v==gr) continue;
        dfs_1(v,s);
        siz[s]+=siz[v];
        if(son[s]==-1||siz[v]>siz[son[s]]) son[s]=v;
    }
}

void dfs_2(int s,int tp){
    top[s]=tp;dfn[s]=++cnt;rnk[cnt]=s;
    if(son[s]==-1) return ;
    dfs_2(son[s],tp);
    for(int i=head[s];i;i=nxt[i]){
        int v=to[i];
        if(v!=son[s]&&v!=fat[s]) dfs_2(v,v);
    }
}

int query(int x,int y){//树剖求链和
    int res=0;
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) Swap(x,y);
        res+=tree.ask(1,dfn[top[x]],dfn[x]);
        x=fat[top[x]];
    }
    res+=tree.ask(1,min(dfn[x],dfn[y]),max(dfn[x],dfn[y]));
    return res;
}

int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    scanf("%d",&q);
    for(int i=1;i<=q;i++){
        scanf("%s%d%d",op+1,&in1,&in2);
        if(op[1]=='b'){
            if(find(in1)==find(in2)) inp[i][0]=9;
            else{inp[i][0]=10;fa[find(in1)]=find(in2);add(in1,in2);add(in2,in1);}//加边
        }
        if(op[1]=='p'){inp[i][0]=1;inp[i][1]=in1;inp[i][2]=in2;}
        if(op[1]=='e'){
            if(find(in1)!=find(in2)) inp[i][0]=11;
            else{inp[i][0]=2;inp[i][1]=in1;inp[i][2]=in2;}
        }
    }
    for(int i=1;i<=n;i++)
        if(!fat[i]){dfs_1(i,0);dfs_2(i,i);}//跑多次树剖
    tree.build(1,1,n);
    for(int i=1;i<=q;i++){//离线操作
        if(inp[i][0]==9){puts("no");continue;}
        if(inp[i][0]==10){puts("yes");continue;}
        if(inp[i][0]==11){puts("impossible");continue;}
        if(inp[i][0]==1){tree.change(1,dfn[inp[i][1]],inp[i][2]);}
        if(inp[i][0]==2){cout<<query(inp[i][1],inp[i][2])<<'\n';}//回答询问
    }
    return 0;
}
posted @ 2023-06-05 16:53  TKXZ133  阅读(20)  评论(0)    收藏  举报