Gym 101161E 主席树+LCA

题意

给一棵树,问树上路径权值中位数。

题解

树上路径权值中位数就是求第k/2大数,很明显主席树+LCA。这道题还是比较容易的,有一点非常重要的是权值W的范围是1-100000,这个范围很大程度上影响了题目的实现难度。由于权值最大只有10万,因此我们可以搞一个10万的主席树,这样的话每一次查询第K大,我们可以利用主席树去针对某一段版本的线段树去查询,从右到左的第K的数字就是第K大。
搞明白了这个,还有一个需要搞明白的就是树上的查询需要用v[a]+v[b]-2*v[lca(a,b)]。这样一减求的就是路径上的值。

代码

#include<bits/stdc++.h>
#define UP(i,l,h) for(int i=l;i<h;i++)
#define DOWN(i,h,l) for(int i=h-1;i>=l;i--)
#define W(t) while(t)
#define MEM(a,b) memset(a,b,sizeof(a))
#define LL long long
#define INF 0x3f3f3f3f
#define EPS 1e-8
#define MAXN 50010
#define MOD 100000007
#define COUT(x) cout<<x<<endl
using namespace std;
struct Edge{
    int to,w;
    Edge(int to=0,int w=0):to(to),w(w){}
};
vector<Edge> vc[MAXN];
const int NN=25001000;
int dep[MAXN],sum[NN],ln[NN],rn[NN],root[MAXN];
int anc[MAXN][25];
int tot;

int update(int o,int L,int l,int r)
{
//    COUT(o<<" "<<l<<" "<<r);
    int k=++tot;
    sum[k]=sum[o],ln[k]=ln[o],rn[k]=rn[o];
    sum[k]++;
    if(l==r) return k;
    int m=(l+r)/2;
    if(L<=m) ln[k]=update(ln[k],L,l,m);
    else rn[k]=update(rn[k],L,m+1,r);
    return k;
}

int query(int a,int b,int f,int L,int l,int r)
{
    if(l==r) return l;
    int m=(l+r)/2;
    int tmp=(sum[ln[b]]+sum[ln[a]]-2*sum[ln[f]]);
    if(L<=tmp) return query(ln[a],ln[b],ln[f],L,l,m);
    else return query(rn[a],rn[b],rn[f],L-tmp,m+1,r);
}

void dfs(int u,int f,int deep){
//    COUT("dfs"<<u);
    dep[u]=deep;
    anc[u][0]=f;
//    COUT(u<<" "<<f);
    UP(i,1,20){
        anc[u][i]=anc[anc[u][i-1]][i-1];
    }
    for(Edge v:vc[u]){
        if(f==v.to) continue;
        root[v.to]=update(root[u],v.w,1,100000);
        dfs(v.to,u,deep+1);
    }
}

int lca(int a,int b){
    if(dep[a]<dep[b]) swap(a,b);
//    COUT(a<<" "<<b);
    DOWN(i,20,0){
//        COUT(dep[anc[a][i]]<<" "<<dep[b]);
        if(dep[b]<=dep[anc[a][i]]) a=anc[a][i];
    }
//    COUT(a<<" "<<b);
    if(a==b) return a;
    DOWN(i,20,0){
        if(anc[a][i]!=anc[b][i]){
            a=anc[a][i];
            b=anc[b][i];
        }
    }
    return anc[a][0];
}

int main(){
    int t;
    scanf("%d",&t);
    W(t--){
        MEM(vc,0);
        MEM(anc,0);
        MEM(dep,0);
        MEM(sum,0);
        MEM(ln,0);
        MEM(rn,0);
        MEM(root,0);
        tot=0;
        int n,q;
        scanf("%d",&n);
        UP(i,0,n-1){
            int u,v,w;
             scanf("%d%d%d",&u,&v,&w);
             vc[u].push_back(Edge(v,w));
             vc[v].push_back(Edge(u,w));
        }
        scanf("%d",&q);
        dfs(1,0,1);
        W(q--){
            int a,b;
            scanf("%d%d",&a,&b);
            int fa=lca(a,b);
//            COUT(dep[a]<<" "<<dep[b]);
//            COUT(fa);
            int cot=dep[a]+dep[b]-2*dep[fa];
//            COUT(cot);
            if(cot%2==1){
                printf("%.1f\n",(double)query(root[a],root[b],root[fa],(cot+1)/2,1,100000));
            }else{
//                COUT((double)query(root[a],root[b],root[fa],cot/2,1,100000)<<" "<<(double)query(root[a],root[b],root[fa],cot/2+1,1,100000));
                printf("%.1f\n",((double)query(root[a],root[b],root[fa],cot/2,1,100000)+(double)query(root[a],root[b],root[fa],cot/2+1,1,100000))/2);
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值