BZOJ3252: 攻略

该博客详细解析了BZOJ3252题目,通过贪心策略和线段树解决寻找树形结构中最大路径和的问题。在选择路径后,路径上点的权值变为0。博主提供了前缀和更新及 dfs 序结合线段树的算法,确保操作复杂度为 O(nlgn)。

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

BZOJ3252: 攻略

贪心·线段树

http://blog.youkuaiyun.com/mys_c_k/article/details/66474976

题目大意:

给定一棵以1为根的n个点的树,树有点权且点权为正整数,可以选择k条以根作为起点的路径,每条路径的价值即这条路径上所有点的点权之和。
但是选择一条路径之后,这条路径上的所有点的点权会变成0。(也就是说,这k条路径中被重复选择的点,其点权只能被计算一次)。
求最大价值之和。n<=200000。

题解:

首先维护一个前缀和,每个点x的前缀和即x到根的所有点权之和。
然后很明显就要贪心的来做了,第一次肯定选取前缀和最大的那个点。
但是选了这个点之后,这个点到根的路径上的所有点的点权被“取走(也就是变成0)”了;
那么观察可知,如果把x这个点取走,辣么说它和它的子树中所有点的前缀和都要减去这个点的权值(注意不是前缀和)。
于是我们每次选取一个前缀和最大的点,从这个点开始往上走一直走到不能走为止,其间对于每个走过的点都进行上述“取走”操作。
显然我们的单次修改操作都是针对一颗子树的,所以显然想到是dfs序。
又因为我们需要维护这样一个数据结构,实现区间减法和询问整个区间的最值,那么显然就是来一发线段树就可以啦~
然后看复杂度,显然每次选择x就把x到根所有的点都取走是不划算的(因为有可能这条路径上的点已经取走了,不用再取一遍了)
于是我们的策略是记录每个点是否被删除,这样选择x的话,从x开始一直往上走,走到一个已经被取过的点就停止。
那么由于每个点只可能被取走一次,取走一次的复杂度是在线段树上进行区间操作的O(lgn),所以复杂度就是O(nlgn)。

Code:

#include <iostream>
#include <cstring>
#include <cstdio>
#define D(x) cout<<#x<<" = "<<x<<"  "
#define E cout<<endl
using namespace std;
typedef long long LL;
const LL N = 200005;
typedef pair<LL,LL> pii;

LL n,K,w[N],s[N],pa[N],pos[N],ptr[N],end[N],tim,ans; bool del[N];

struct Edge{
    LL to,next;
} e[N*2];
LL ec=0, head[N];
void add(LL a,LL b){
    ec++; e[ec].to=b; e[ec].next=head[a]; head[a]=ec;
}

void dfs(LL x,LL f){
    s[x]=s[f]+w[x]; pa[x]=f;
    pos[x]=++tim; ptr[pos[x]]=x;
    for(LL i=head[x];i;i=e[i].next){
        LL v=e[i].to;
        if(v==f) continue;
        dfs(v,x);
    }
    end[x]=tim;
}

struct Node{
    LL l,r,tag;
    pii mx;
} pool[N*4];

void update(LL x){
    Node &t=pool[x];
    if(t.l!=t.r){
        pii tp=max(pool[x*2].mx,pool[x*2+1].mx);
        tp.first+=t.tag; t.mx=tp;
    }
    else{ t.mx=make_pair(t.tag,t.l); }
}

void build(LL x,LL l,LL r){
    Node &t=pool[x]; 
    t.l=l; t.r=r; t.tag=0;
    if(l!=r){
        LL mid=(l+r)>>1;
        build(x*2,l,mid);
        build(x*2+1,mid+1,r);
    }
    else{ t.tag=s[ptr[l]]; }
    update(x);
}

void change(LL x,LL ql,LL qr,LL d){
    Node &t=pool[x];
    if(ql<=t.l && t.r<=qr){ t.tag+=d; }
    else{
        LL mid=(t.l+t.r)>>1;
        if(ql<=mid) change(x*2,ql,qr,d);
        if(qr>mid) change(x*2+1,ql,qr,d);
    }
    update(x);
}

int main(){
    freopen("a.in","r",stdin);
    scanf("%lld%lld",&n,&K);
    for(LL i=1;i<=n;i++) scanf("%lld",&w[i]);
    LL a,b;
    for(LL i=1;i<n;i++){
        scanf("%lld%lld",&a,&b);
        add(a,b); add(b,a);
    }
    dfs(1,0);
    build(1,1,n);
    while(K--){
        for(LL p=ptr[pool[1].mx.second];p && !del[p];p=pa[p]){
//          D(p); E;
            ans+=w[p]; del[p]=true;
            change(1,pos[p],end[p],-w[p]);
        }
    }
    printf("%lld\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值