HDU 5692(DFS序,线段树)

本文介绍了一种基于线段树和DFS序的算法,用于解决小度熊在百度科技园内的零食机间寻找价值总和最大的路线问题。通过构建线段树并维护节点到根节点的权值和,实现高效的路径更新与查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

problem

百度科技园内有n个零食机,零食机之间通过n−1条路相互连通。每个零食机都有一个值v,表示为小度熊提供零食的价值。

由于零食被频繁的消耗和补充,零食机的价值v会时常发生变化。小度熊只能从编号为0的零食机出发,并且每个零食机至多经过一次。另外,小度熊会对某个零食机的零食有所偏爱,要求路线上必须有那个零食机。

为小度熊规划一个路线,使得路线上的价值总和最大。

Input

输入数据第一行是一个整数T(T≤10),表示有T组测试数据。

对于每组数据,包含两个整数n,m(1≤n,m≤100000),表示有n个零食机,m次操作。

接下来n−1行,每行两个整数x和y,(0≤x,y

Output

对于每组数据,首先输出一行”Case #?:”,在问号处应填入当前数据的组数,组数从1开始计算。

对于每次询问,输出从编号为0的零食机出发,必须经过编号为xx零食机的路线中,价值总和的最大值。

Sample Input

1
6 5
0 1
1 2
0 3
3 4
5 3
7 -5 100 20 -5 -7
1 1
1 3
0 2 -1
1 1
1 5

Sample Output

Case #1:
102
27
2
20


思路

先把这棵树dfs求出dfs序以建成线段树,in[x],out[x]分别表示x的子树节点的左右区间。
维护一个presum[]作为一开始每个节点到根节点的权值和
后面的update直接调整change=val-cost[]值即可

理解:树上节点的DFS序满足父系节点区间覆盖子系节点。因此可以用in,out这个区间来对线段树修改,实际上修改的就是这个结点的子树(一个点权值改变,它的子树到根的路径权值和跟着改变)。对于本题,val[rt]维护的是经过该节点的权值最大路径,即题目所求
所以,对于修改,对区间内点的val进行加减;对于询问,只要求出询问结点区间内所有结点的val最大值即可。
可能不理解的地方:如果取值都为正,那自然到叶子结点了。这里权值是可能为负的,所以不一定到哪。对于每一个询问,找到in和out即找到了它的子节点们,因此可以用线段树来维护最大值,且这个最大值对应的路径一定经过询问结点:-D。

dfs代码:

void dfs(int rt,int f){
    in[rt]=++Clock;
    presum[rt]=presum[f]+cost[rt];//维护当前节点到根节点的权值和
    Hash[Clock]=rt;//将树中的编号hash成dfs序的映射
    //因为dfs时用树的编号易求前缀和presum
    //然后hash后 方便后面线段树调用presum
    for(int i=head[rt];i!=-1;i=edges[i].to){
        int tt=edges[i].from;
        if(tt!=f) dfs(tt,rt);
    }
    out[rt]=Clock;
}


代码示例

#include<bits/stdc++.h>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
using namespace std;
typedef __int64 ll;

const int maxn=100100;
struct Edge{
    int from,to;
}edges[maxn<<1];

int head[maxn<<1],in[maxn],out[maxn],Hash[maxn];
int Clock,cnt;
ll presum[maxn],cost[maxn];
ll lazy[maxn<<2],val[maxn<<2];//val维护区间最值   lazy标记

void edge_add(int u,int v){
    edges[cnt].from=v;
    edges[cnt].to=head[u];
    head[u]=cnt++;
}

void init(){
    memset(head,-1,sizeof(head));
    memset(Hash,0,sizeof(Hash));
    memset(presum,0,sizeof(presum));
    memset(cost,0,sizeof(cost));
    memset(lazy,0,sizeof(lazy));
    memset(val,0,sizeof(val));
    memset(edges,0,sizeof(edges));
    cnt=0;
    Clock=0;
}

void dfs(int rt,int f){
    in[rt]=++Clock;
    presum[rt]=presum[f]+cost[rt];//维护当前节点到根节点的权值和
    Hash[Clock]=rt;//将树中的编号hash成dfs序的映射
    //因为dfs时用树的编号易求前缀和presum
    //然后hash后 方便后面线段树调用presum
    for(int i=head[rt];i!=-1;i=edges[i].to){
        int tt=edges[i].from;
        if(tt!=f) dfs(tt,rt);
    }
    out[rt]=Clock;
}

void PushUp(int rt){
    val[rt]=max(val[rt<<1],val[rt<<1|1]);
}

void PushDown(int rt){
    if(lazy[rt]){
        lazy[rt<<1]+=lazy[rt];
        lazy[rt<<1|1]+=lazy[rt];
        val[rt<<1]+=lazy[rt];
        val[rt<<1|1]+=lazy[rt];
        lazy[rt]=0;
    }
}

void build(int l,int r,int rt){
    val[rt]=lazy[rt]=0;
    if(l==r){
        val[rt]=presum[Hash[l]];//赋初值,表示每个点的ans(到根的点权和)
        return ;
    }
    int m=(l+r)>>1;
    build(lson);
    build(rson);
    PushUp(rt);//最后push_up
}

void update(ll v,int L,int R,int l,int r,int rt){
    if(l>R||r<L) return ;
    if(L<=l&&R>=r){
        val[rt]+=v;
        lazy[rt]+=v;
        return ;
    }
    PushDown(rt);//下面要往下递归,所以要释放标记
    int m=(l+r)>>1;
    update(v,L,R,lson);
    update(v,L,R,rson);
    PushUp(rt);
}

ll Query(int L,int R,int l,int r,int rt){
    if(L<=l&&R>=r) return val[rt];
    PushDown(rt);
    ll ans=-1e18;
    int m=(l+r)>>1;
    if(L<=m) ans=max(ans,Query(L,R,lson));
    if(R>m) ans=max(ans,Query(L,R,rson));
    return ans;
}

int main()
{
    int t,n,m;
    scanf("%d",&t);
    for(int kase=1;kase<=t;kase++){//节点从1编号
        printf("Case #%d:\n",kase);
        init();
        scanf("%d %d",&n,&m);
        int u,v,x,op;
        ll val;
        for(int i=1;i<n;++i){
            scanf("%d %d",&u,&v);
            u++,v++;//节点从1编号
            edge_add(u,v);
            edge_add(v,u);
        }
        for(int i=1;i<=n;++i) scanf("%I64d",&cost[i]);
        presum[0]=0;
        dfs(1,0);
        build(1,Clock,1);
        for(int i=0;i<m;++i){
            scanf("%d",&op);
            if(op==1){
                scanf("%d",&x);
                x++;//节点从1编号
                printf("%I64d\n",Query(in[x],out[x],1,Clock,1));
            }
            else{
                scanf("%d %I64d",&x,&val);
                x++;//节点从1编号
                ll change=val-cost[x];
                update(change,in[x],out[x],1,Clock,1);
                cost[x]=val;
            }
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值