VIJOS 1985 小h的妹子树一

题意简述

给定n个节点组成的森林,需要支持两个操作
Q u v:询问泡u和v的最小代价
L u v:连接u和v
对于100%的数据,保证:1<=n<=100000,1<=操作个数m<=200000,1<=Vi<=1000

分析

对于询问,通过树上倍增计算就可以了

对于连接操作,采取启发式合并
将较小的集合连接到较大的集合中去,然后以边的端点为根暴力重构小集合的树

以每个点为研究对象分析

一个点被重构之后,其所在联通快的大小至少扩大为原来的2倍
因此一个点最多被重构log n次
因此所有点合并的复杂度为O(n log n)
总复杂度O((n+m)log n)

代码

#include<cstdio>
#define fo(i,a,b) for(int i=a;i<=b;i++)
const int mn=100000+50,bi=17;
int n,fat[mn],va[mn],u,v,bs,bb[bi+1],bt[mn],tot[mn],f[mn][18],d[mn][bi+1],et,be[mn],ans,m,de[mn];
char ch,cha[10];
struct edge{
    int v,ne;
};
edge e[mn*2];
void add_edge(int u,int v){
    e[++et].ne=be[u];
    e[et].v=v;
    be[u]=et;
}
void build_tree(int r,int fa){//dfs建树  求出倍增数组  代表元素  集合大小  深度 
    fo(i,1,bi){
        f[r][i]=f[f[r][i-1]][i-1];
        if (!f[r][i])
            break;
        d[r][i]=d[f[r][i-1]][i-1]+d[r][i-1];
        //printf("%d:%d %d\n",i,f[r][i],d[r][i]);输出调试 
    }
    int i=be[r];
    while (i){
        if (e[i].v!=fa){//递归儿子 
            de[e[i].v]=de[r]+1;
            fat[e[i].v]=fat[r];
            f[e[i].v][0]=r;//
            d[e[i].v][0]=va[r];
            build_tree(e[i].v,r);
            tot[r]+=tot[e[i].v];
        }
        i=e[i].ne;
    }

}
int lca(int x,int y){//求lca 
    int ans=0,dd;
    if (de[x]<de[y])
        x^=y^=x^=y;
    dd=de[x]-de[y];
    ans+=va[x];
    fo(i,0,bi){
        if (dd&bb[i]){
            ans+=d[x][i];
            x=f[x][i];
        }
        if (dd<bb[i])
            break;
    }
    if (x==y)
        return ans;
    ans+=va[y];
    for(int i=bi;i>=0;i--)
        if ((f[x][i])&&(f[x][i]!=f[y][i])){
            ans+=d[x][i];
            ans+=d[y][i];
            x=f[x][i];
            y=f[y][i];
        }
    return ans+va[f[x][0]];
}
int main(){
    fo(i,0,bi)//1..bi
        bb[i]=1<<i;
    scanf("%d",&n);
    fo(i,1,n)
        scanf("%d",&va[i]);
    fo(i,1,n){
        scanf("%d%d",&u,&v);
        if (u==0)
            bt[++bs]=v;
        if (v==0)
            bt[++bs]=u;
        if ((u)&&(v)){
            add_edge(v,u);
            add_edge(u,v);
        }
    }
    fo(i,1,bs){
        tot[bt[i]]=1;
        fat[bt[i]]=bt[i];
        de[bt[i]]=1;
        build_tree(bt[i],-1);
    }
    scanf("%d",&m);
    fo(i,1,m){
        scanf("%s",cha);
        scanf("%d%d",&u,&v);
        u^=ans;v^=ans;
        if (cha[0]=='Q'){
            ans=lca(u,v);
            printf("%d\n",ans);
        }
        if (cha[0]=='L'){
            add_edge(u,v);
            add_edge(v,u);
            if (tot[fat[u]]<tot[fat[v]]){
                tot[fat[v]]+=tot[fat[u]];
                fat[u]=fat[v];
                de[u]=de[v]+1;
                f[u][0]=v;
                d[u][0]=va[v];
                build_tree(u,v);
                continue;
            }
                tot[fat[u]]+=tot[fat[v]];
                fat[v]=fat[u];
                de[v]=de[u]+1;
                f[v][0]=u;
                d[v][0]=va[u];
                build_tree(v,u);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值