bzoj3252 攻略 dfs序+线段树

  题目传送门

  题目大意:给出一棵树,1为根节点,每个节点都有权值,每个叶子节点都是一个游戏的结局,选择k个游戏结局,使得权值总和最大,同一个节点不会被重复计算。

  思路:这道题最关键的是要想到一个性质,就是不管怎么选,当前子树中,叶子节点最重的那一条链肯定要被选择。(可以反证,如果不选这条链,一定不是最优的),那么贪心的想一下,选取这条链之后,把这条链上的所有节点权值变成0,得到的新的树,重复以上过程k次,就得到了我们要的答案。

  要怎么把链节点的权值变成0呢?我们建立线段树的时候,把以每个节点当成末尾节点的链当成一个元素,用这个节点的dfs序当成线段树的序号,做一个可以区间修改,查询最大值并返回下标的线段树,这样每次查询(1,n)区间最大值,就是当前最重的链,得到链后,要把这条链上每一个节点都赋值为0,并且更新其他相关链的信息。我们发现,把一个节点赋值为0,其实就是把这个节点的子树都减去此节点权值,那么就用dfs序进行区间减处理,并且把每个节点是否被删去都标记一下。如果这条链往上走的节点已经被清空了,就不需要再往上走了。这样我们会发现,每个节点最多被删除一次,每一次修改的时间复杂度是logn,所以总复杂度是nlogn,和 k 几乎无关。注意dfs序和原下标的对应。

上代码(bzoj iostream头文件居然ce了。。)

  

#include<cstdio>
#include<cstring>
#include<stdlib.h>
#include<cstdio>
#include<algorithm>
//#include<iostream>
#include<map>
#include<queue>
#include<vector>
#define CLR(a,b) memset(a,b,sizeof(a))
#define lson l , mid , x << 1
#define rson mid + 1 , r , x << 1 | 1
#define PI acos(-1)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int maxn=200010;
struct edge{
    int to,Next;
}e[2*maxn];
int n,m;
int tot,head[maxn],u,v,L[maxn],R[maxn],time,dfn[maxn],fa[maxn],ref[maxn];
ll val[maxn],dis[maxn];
bool vis[maxn];
inline void init(){
    CLR(head,-1),tot=0,time=0;
    CLR(val,0);
    dis[0]=0;
    CLR(vis,0);
}
ll mx[maxn << 2] , tag[maxn << 2],mp[ maxn << 2];

inline void pushup(int x)
{
    int l = x << 1 , r = x << 1 | 1;
    if(mx[l] > mx[r]) mx[x] = mx[l] , mp[x] = mp[l];
    else mx[x] = mx[r] , mp[x] = mp[r];
}
inline void pushdown(int x)
{
    if(tag[x])
    {
        int l = x << 1 , r = x << 1 | 1;
        mx[l] -= tag[x] , mx[r] -= tag[x];
        tag[l] += tag[x] , tag[r] += tag[x];
        tag[x] = 0;
    }
}
inline void build(int l , int r , int x)
{
    if(l == r)
    {
        mx[x] = dis[l] , mp[x] = l;
        return;
    }
    int mid = (l + r) >> 1;
    build(lson) , build(rson);
    pushup(x);
}
inline void update(int b , int e , ll a , int l , int r , int x)
{
    if(b <= l && r <= e)
    {
        mx[x] -= a , tag[x] += a;
        return;
    }
    pushdown(x);
    int mid = (l + r) >> 1;
    if(b <= mid) update(b , e , a , lson);
    if(e > mid) update(b , e , a , rson);
    pushup(x);
}

inline void addv(int u,int v){
    e[++tot]={v,head[u]};
    head[u]=tot;
}

inline void dfs(int u,int pre){
    fa[u]=pre;
    dfn[++time]=u;
    ref[u]=time;
    L[u]=time;
    dis[time]=val[u]+dis[ref[pre]];
    for(int i=head[u];i!=-1;i=e[i].Next)
    {
        int v=e[i].to;
        if(v==pre)continue;
        dfs(v,u);
    }
    R[u]=time;
}
int main(){
    int k;
    while(scanf("%d%d",&n,&k)!=EOF)
    {
        init();
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&val[i]);
        }
        for(int i=1;i<n;i++)
        {
            scanf("%d%d",&u,&v);
            addv(u,v);
            addv(v,u);
        }
        
        dfs(1,0);
        build(1,n,1);
        ll ans=0;
        int x;
    
        while(k--)
        {
            ans+=mx[1],x=mp[1];
            while(x && !vis[x])
            {
                update(L[dfn[x]],R[dfn[x]],val[dfn[x]],1,n,1);
                vis[x]=1;
                x=ref[fa[dfn[x]]];
                
            }
        }
        printf("%lld
",ans);
        
    }
}

Description

题目简述:树版[k取方格数]
众所周知,桂木桂马是攻略之神,开启攻略之神模式后,他可以同时攻略k部游戏。今天他得到了一款新游戏《XX
半岛》,这款游戏有n个场景(scene),某些场景可以通过不同的选择支到达其他场景。所有场景和选择支构成树状
结构:开始游戏时在根节点(共通线),叶子节点为结局。每个场景有一个价值,现在桂马开启攻略之神模式,同
时攻略k次该游戏,问他观赏到的场景的价值和最大是多少(同一场景观看多次是不能重复得到价值的)
“为什么你还没玩就知道每个场景的价值呢?”
“我已经看到结局了。”

Input

第一行两个正整数n,k
第二行n个正整数,表示每个场景的价值
以下n-1行,每行2个整数a,b,表示a场景有个选择支通向b场景(即a是b的父亲)
保证场景1为根节点
n<=200000,1<=场景价值<=2^31-1

Output

输出一个整数表示答案

Sample Input

5 2
4 3 2 1 1
1 2
1 5
2 3
2 4

Sample Output

10
原文地址:https://www.cnblogs.com/mountaink/p/9878918.html