HDU3804------树链剖分+线段树

想把这一道典型题给发出来,但是无奈一直没有找到题,直到最近才找到的。
原题链接
问题描述
树上有一些有n个节点的查询。每个查询都被描述为两个整数(X,Y)。对于每个查询,您应该找到集合E中边的最大权重,它满足以下两个条件。
1)边缘必须在从节点X到节点1的路径上
.2)边缘的权重应该小于或等于Y.
现在给你树和查询。你能找到每个查询的答案吗?

输入
输入的第一行是整数T,表示测试用例的数量。对于每种情况,第一行包含整数n表示树中的节点数。然后是n-1行,每行包含三个整数X,Y,W表示节点X和节点Y之间的边,其值为W.然后一行有一个整数Q表示查询数。在接下来的Q行中,每行包含两个整数X和Y,如上所述。

产量
对于每个测试用例,您应该输出Q行。如果没有边满足上述条件,则只输出“-1”进行此查询。否则输出此查询的答案。

输入数据
1
3
1 2 7
2 3 5
4
3 10
3 7
3 6
3 4

输出数据
7
7
5
-1

#include<bits/stdc++.h>
#define lson k<<1,l,mid
#define rson k<<1|1,mid+1,r
using namespace std;
const int MX=2e5+9;
int cnt,tot,T,n,Q,u,v,w,ans[MX];
int fa[MX],top[MX],siz[MX],son[MX],head[MX],de[MX],p[MX],t[MX<<2];
struct node{
    int u,v,next,w;
}edge[MX],e[MX];

bool cmp1(node &a,node &b){
    return a.w<b.w;
}

struct Node{
    int x,y,id;
}q[MX];

bool cmp2(Node &a,Node &b){
    return a.y<b.y;
}

void init(){
    tot=cnt=0;
    memset(siz,0,sizeof(siz));
    memset(head,-1,sizeof(head));
    memset(t,0,sizeof(t));
}

void add(int u,int v,int w){   
    edge[cnt].u=u;
    edge[cnt].v=v;
    edge[cnt].w=w;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}

void dfs1(int u,int depth,int father){
    fa[u]=father,de[u]=depth,siz[u]=1,son[u]=-1;  
    for( int i=head[u] ; ~i ; i=edge[i].next ){
        if( edge[i].v!=father ){   // 不能往后面走
            dfs1(edge[i].v,depth+1,u);  
            siz[u]+=siz[edge[i].v];  //siz是为了求重链的,就一工具数组
            if( son[u]==-1 || siz[son[u]]<siz[edge[i].v] )    // 求u的子链中的重链,
                son[u]=edge[i].v;
        }
    }
    return ;
}

void dfs2(int u,int tp){
    p[u]=++tot;   //在线段树里面的标号
    top[u]=tp;
    if( son[u]>0 )
        dfs2(son[u],tp);    //所有的重链是拥有同一个top的
    for( int i=head[u] ; ~i ; i=edge[i].next )
        if( edge[i].v!=son[u] && edge[i].v!=fa[u] )   //不可以走重链,不可以走父链
            dfs2(edge[i].v,edge[i].v);   // 这里是2个edge[i].v!!!
    return ;
}

void ins(int k,int l,int r,int pos,int w){
    if( l==r ){
        t[k]=w;
        return ;
    }
    int mid=(l+r)>>1;
    if( pos<=mid )
        ins(lson,pos,w);
    else
        ins(rson,pos,w);
    t[k]=max(t[k<<1],t[k<<1|1]);
}

int que(int k,int l,int r,int L,int R){
    if( L<=l && r<=R )
        return t[k];
    int mid=(l+r)>>1,ans=0;
    if( L<=mid )
        ans=max(ans,que(lson,L,R));
    if( mid<R )
        ans=max(ans,que(rson,L,R));
    return ans;
}

int vs(int u,int v){
    int f1=top[u],f2=top[v],res=-1;
    while( f1!=f2 ){
        if( de[f1]>de[f2] ){    // 经过多次向上走之后,永远保证v的top(顶点)的dp(深度)是u,v中较深的
            swap(f1,f2);
            swap(u,v);    
        }
        res=max(res,que(1,1,n,p[f2],p[v]));
        v=fa[f2];
        f2=top[v];
    }
    if( u==v )
        return res;
    if( de[v]<de[u] )   // 当走出while之后,u,v一定是在同一条重链上,可能u==v,也可能u!=v。
        swap(u,v);
    res=max(res,que(1,1,n,p[u],p[v]));
    return res;
}

void solve(){
    scanf("%d",&Q);
    for( int i=1 ; i<=Q ; i++ ){
        scanf("%d %d",&q[i].x,&q[i].y);
        q[i].id=i;
    }
    for( int i=0 ; i<n-1 ; i++ )
        e[i]=edge[i*2];
    sort(e,e+n-1,cmp1);
    sort(q+1,q+Q+1,cmp2);
    int k=0;
    for( int i=1 ; i<=Q ; i++ ){
        while( k<=n-1 && e[k].w<=q[i].y ){   //每一次只加入小于y的节点到线段树中
            int pos;
            if( de[e[k].u]<de[e[k].v] )
                pos=e[k].v;
            else
                pos=e[k].u;
            ins(1,1,n,p[pos],e[k].w);   //w所对应的线段树中的节点必须是dp较小的,
            k++;
        }
        int res=vs(1,q[i].x);   // 求2节点之间的距离
        if( res==0 )
            res=-1;
        ans[q[i].id]=res;
    }
    for( int i=1 ; i<=Q ; i++ )
        printf("%d\n",ans[i]);
    return ;
}

int main()
{
    freopen("input.txt","r",stdin);
    scanf("%d",&T);
    while( T-- ){
        init();
        scanf("%d",&n);
        for( int i=1 ; i<=n-1 ; i++ ){
            scanf("%d %d %d",&u,&v,&w);
            add(u,v,w),add(v,u,w);
        }
        dfs1(1,1,0);
        dfs2(1,1);
        solve();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值