HDU 5242 Game 上海大都会赛 G题

HDU 5242 Game 上海大都会赛 G题

比赛的时候写的线段树,结果对这个板理解不够,写错了一直浪费三个小时。还是太菜  T^T


题目描述:
    在一颗树上选定K条从根到叶子的路径,求经过的最大的点权和
    数据范围 N,K <= 1e5

思路:
    贪心.
    一个很直观的想法就是每次选取一条点权和最大的路径,然后将这条路径上的点权置零,重复贪心K次得到答案
    维护的方法有很多种:
 1°排序,树DP每颗子树的最大路径,然后把所有的节点按权值和从大到小排序,
          接下来按照排序的顺序贪心,如果当前点的父亲已经枚举过了且没有另外的儿子出现,说明当前点一定是之前某条路径上出现过的点
          如果当前点的父亲没有枚举过,或者当前点的父亲已经有儿子枚举过,计算一次答案,计算K次得到答案,具体实现用一个数组累积出现次数
 2°优先队列,树DP每颗子树的最大路径,把所有节点放入一个大根堆
              每次取出堆顶元素计算一次答案,然后删去堆中这条路径上每一个点,计算K次得到答案
 3°DFS序列 + 线段树,DFS根到每个节点的权值和,用DFS序列建一颗树
                      每次查询一个最大的单点,沿着这个点到根的路径依次删去子树下的DFS序列中每个点的权值(相当于将这条路径上的点权置零)
                      查询M次得到答案,每个点只会被删去一次,时间复杂度也是O(N·logN)
 4°树的轻重链剖分,按权值剖分完后排序取前K条路径,效果和第一种方法是一样的

排序做法:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
#define foru(i, a, b) for(int i=(a); i<=(b); i++)
#define ford(i, a, b) for(int i=(a); i>=(b); i--)
#define clr(a, b) memset(a, b, sizeof(a));
typedef long long ll;

const int N = 1e5 + 10;

int n, m;
int adj[N], nxt[N], aim[N], tot;
int fa[N], use[N];
ll val[N], f[N];

struct node{
    int idx;
    ll dp;
}e[N];

void add(int a, int b){
    aim[++tot] = b;
    nxt[tot] = adj[a];
    adj[a] = tot;
}

void dfs(int x){
    for(int k = adj[x]; k; k = nxt[k]){
        dfs(aim[k]);
        f[x] = max(f[x], f[aim[k]]);
    }
    f[x] += val[x];
}

bool cmp(node A, node B){
    return A.dp > B.dp;
}

int main(){
    //freopen("G.txt", "r", stdin);
    int T, cas = 0; scanf("%d", &T);
    while(T--){
        tot = 0; clr(adj, 0);
        scanf("%d %d", &n, &m);
        foru(i, 1, n) scanf("%lld", &val[i]);
        foru(i, 2, n){
            int a, b; scanf("%d %d", &a, &b);
            add(a, b); fa[b] = a;
        }
        clr(f, 0); dfs(1);
        foru(i, 1, n) e[i].idx = i, e[i].dp = f[i];
        sort(e + 1, e + 1 + n, cmp);
        clr(use, 0); ll ans = 0;
        foru(i, 1, n){
            int x = e[i].idx; use[x] ++;
            if (use[fa[x]] == 1){
                use[fa[x]] ++;
                continue;
            }
            m --;
            ans += e[i].dp;
            if (! m) break;
        }
        printf("Case #%d: %lld\n", ++cas, ans);
    }
    return 0;
}

线段树做法:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
#define foru(i, a, b) for(int i=(a);i<=(b);i++)
#define ford(i, a, b) for(int i=(a);i>=(b);i--)
#define clr(a, b) memset(a, b, sizeof(a));
typedef long long ll;

const int N = 100010;

int n, m;
int adj[N], aim[N], nxt[N], tot;
int st[N], en[N], fa[N], pos[N], tim;
ll val[N], sum[N], Max[4*N], dt[4*N], cnt[N], AIM, ANS;
int L[4*N], R[4*N], mid[4*N], now;

void add(int a, int b){
    aim[++tot] = b;
    nxt[tot] = adj[a];
    adj[a] = tot;
}

void dfs(int x){
    st[x] = ++tim;
    pos[tim] = x;
    cnt[tim] = sum[x];
    for(int k = adj[x]; k; k = nxt[k])
        sum[aim[k]] = sum[x] + val[aim[k]], dfs(aim[k]);
    en[x] = tim;
}

void build(int i, int l, int r){
    L[i] = l; R[i] = r;
    if (l == r){
        Max[i] = cnt[l];
        return;
    }
    mid[i] = (l + r) >> 1;
    build(i << 1, l, mid[i]);
    build((i<<1)+1, mid[i]+1, r);
    Max[i] = max(Max[i<<1], Max[(i<<1)+1]);
}

void query(int i, ll add){
    if (L[i] == R[i]){
        AIM = Max[i] + add;
        now = pos[L[i]];
        return;
    }
    if (Max[i << 1] > Max[(i<<1)+1]) query(i << 1, add + dt[i]);
        else query((i << 1) + 1, add + dt[i]);
}

void maintain(int i){
    if (R[i] > L[i])
        Max[i] = max(Max[i<<1], Max[(i<<1)+1]);
    else
        Max[i] = cnt[L[i]];  // 初始值
    Max[i] += dt[i];
}

void updata(int i, int st, int en, ll add){
    if (st <= L[i] && R[i] <= en){
        dt[i] += add;
        maintain(i);
        return;
    }
    if (st <= mid[i]) updata(i<<1, st, en, add);
    if (en >  mid[i]) updata((i<<1)+1, st, en, add);
    maintain(i);
}

int main(){
    freopen("G.txt", "r", stdin);
    int T, cas = 0; scanf("%d", &T);
    while(T--){
        tot = 0; clr(adj, 0);
        scanf("%d %d", &n, &m);
        foru(i, 1, n) scanf("%lld", &val[i]);
        foru(i, 2, n){
            int a, b; scanf("%d %d", &a, &b);
            add(a, b); fa[b] = a;
        }
        tim = 0; sum[1] = val[1]; dfs(1);
        clr(dt, 0); build(1, 1, n); ANS = 0;

        while(m --){
            query(1, 0);
            if (AIM <= 0) break;
            ANS += AIM;
            while (now != 0){
                if (sum[now] <= 0) break;
                updata(1, st[now], en[now], -val[now]);
                sum[now] = 0;
                now = fa[now];
            }
        }
        printf("Case #%d: %lld\n", ++cas, ANS);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值