HDU - 3804:Query on a tree(线段树+离线+树链剖分)


题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3804


题目大意:

给你一棵树,树上每个边有一个权值,然后给你一些查询操作, 输入p,q,查询结点p到结点q之间的边中权值不超过q的最大值。


解题思路:

这几天学习了树链剖分,从一脸蒙蔽到开始手打模板,感觉终于理解的还算行了,起码基础的东西都知道了,接下来就是多刷一些题熟练一下了。


这道题也是最近做的唯一不是裸树链剖分的题目(不过也差不多),刚开始写了个裸的,正常维护了一颗线段树,就是查询的时候加了个小判断条件,结果理所当然的 t 掉了。然后没办法感觉有点没法解决就去discuss里面看了一下,好吧,竟然要用离线,还是对离线不够敏感,也是这一类的题目做的比较少,以后要专门的练习一下。


离线的方法呢就是将边的权值从小到大排序,再将查询(p,q)按q的大小从小到大排序。这里进行每一次查询操作之前将权值比 q 小的边加入线段树更新就行了,剩下就是树链剖分和线段树的模板了,以下贴代码:


#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <queue>
#include <map>
#include <algorithm>
#include <set>
#include <functional>
#define pb push_back
#define lson rt<<1
#define rson rt<<1|1
using namespace std;
typedef long long ll;
const int N=100010;
const int INF=1e9+7;
int cnt,n,m,pos,sp,sq,step,ans;
int dep[N],size[N],son[N],top[N],w[N],fa[N],res[N];
int head[N];
struct node     //链表建边
{
    int u,v,w;
    int next;
    bool operator<(const node s) const
    {
        return w<s.w;
    }
}edge[N*2],e[N];
struct que  //询问
{
    int st,sk,id;
    bool operator<(const que q) const
    {
        return sk<q.sk;
    }
}qu[N];
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++;
}
struct tree
{
    int l,r,mid;
    int ma;
}t[N<<2];
void dfs1(int u,int fat,int de)     //树链剖分模板在此不详解了
{
    dep[u]=de;fa[u]=fat;size[u]=1;son[u]=0;
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(v==fa[u])
            continue;
        dfs1(v,u,de+1);
        size[u]+=size[v];
        if(son[v]==-1||(size[son[u]]<size[v]))
            son[u]=v;
    }
    return ;
}
void dfs2(int u,int tp)
{
    top[u]=tp;
    w[u]=++step;
    if(son[u]>0)
        dfs2(son[u],tp);
    for(int i=head[u];i!=-1;i=edge[i].next)
    {
        int v=edge[i].v;
        if(v==fa[u]||v==son[u])
            continue;
        dfs2(v,v);
    }
    return ;
}
void build(int l,int r,int rt)
{
    int m=(l+r)>>1;
    t[rt].l=l;t[rt].r=r;
    t[rt].mid=m;
    t[rt].ma=-INF;
    if(l==r)
        return ;
    build(l,m,lson);
    build(m+1,r,rson);
}
void pushup(int rt)     //正常的pushup
{
    t[rt].ma=max(t[lson].ma,t[rson].ma);
}
void query(int p,int q,int rt)      //正常的线段树查询
{
    if(t[rt].l>=p&&t[rt].r<=q)
    {
        ans=max(t[rt].ma,ans);
        return ;
    }
    if(p<=t[rt].mid)
        query(p,q,lson);
    if(q>t[rt].mid)
        query(p,q,rson);
    pushup(rt);
}
void update(int pos,int flag,int rt)    //正常的线段树更新
{
    if(t[rt].l==t[rt].r)
    {
        t[rt].ma=flag;
        return ;
    }
    if(pos<=t[rt].mid)
        update(pos,flag,lson);
    if(pos>t[rt].mid)
        update(pos,flag,rson);
    pushup(rt);
}
void vs(int u,int v)    //查询u到v之间的合法区间
{
    int f1=top[u],f2=top[v];
    ans=-INF;
    while(f1!=f2)
    {
        if(dep[f2]>dep[f1])
        {
            swap(f1,f2);
            swap(u,v);
        }
        query(w[f1],w[u],1);
        u=fa[f1];
        f1=top[u];
    }
    if(u==v)
        return ;
    if(dep[u]>dep[v])
        swap(u,v);
    query(w[son[u]],w[v],1);
}
void slove()
{
    build(0,step,1);    //建立线段树
    for(int i=0;i<n-1;i++)  //为了方便先将边储存起来
        e[i]=edge[i*2];
    e[n-1].w=INF;   //这里赋为INF下面方便
    sort(e,e+n-1);
    scanf("%d",&m);
    for(int i=1;i<=m;i++)   //储存询问
    {
        scanf("%d%d",&qu[i].st,&qu[i].sk);
        qu[i].id=i;
    }
    sort(qu+1,qu+1+m);
    int k=0;
    for(int i=1;i<=m;i++)
    {
        while(e[k].w<=qu[i].sk) //在查询前将权值比qu[i].sk小的边加入线段树
        {
            if(dep[e[k].u]>dep[e[k].v])
                swap(e[k].u,e[k].v);
            update(w[e[k].v],e[k].w,1);
            k++;
        }
        vs(qu[i].st,1);
        if(ans==-INF)
            res[qu[i].id]=-1;
        else
            res[qu[i].id]=ans;
    }
    for(int i=1;i<=m;i++)
        printf("%d\n",res[i]);
}
int main()
{
    int QAQ;
    scanf("%d",&QAQ);
    while(QAQ--)
    {
        cnt=0;step=0;
        memset(head,-1,sizeof head);
        int u,v,w;
        scanf("%d",&n);
        for(int i=1;i<n;i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);
            add(v,u,w);
        }
        dfs1(1,0,1);
        dfs2(1,1);
        slove();
    }
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值