2019 ICPC North America Qualifier F. Prospecting

题意:

给一颗n点树,有边权和点权。边看成堆着土的隧道,土的数量就是边权,如果土的数量不为0,那么这条边不可以走,挖走1个土消耗1块钱。点上的权值x代表走到这个点能得到x块钱。有一个需要抵达的终点。
起点为0。从起点出发,可以选择一个能走到的隧道并挖隧道里面的土。可以不用挖完。走到点上一定会获得点权的钱。如果当前钱的数量小于0就破产
问最少需要多少钱可以走到终点
问至少带多少钱可以让你在最坏的情况下依然可以到达终点。
x[i]表示点i的权值,y[i]表示i的父亲边的权值
n < = 1 e 5 n <= 1e5 n<=1e5

解题思路:

先看最坏情况:
用dp[i]表示走i的父亲边最多可以花掉多少钱
那么 d p [ i ] = m a x ( y [ i ] − 1 , y [ i ] − x [ i ] + ∑ d p [ v ] ) dp[i] = max(y[i]-1, y[i]-x[i]+\sum dp[v]) dp[i]=max(y[i]1,y[i]x[i]+dp[v])
其中v是i的儿子。把终点的点权设置为无穷大,那么dp[0]+1就是答案了。

再看最好情况花多少钱。
首先对于一个子树i,我们想知道它最少花多少钱能赚到钱。

  1. 如果x[i] > y[i], 进入i至少要花y[i],可以赚到x[i]-y[i]元,而它的子树部分都没有处理,但是这不重要。因为我们知道了进入i最少要花多少钱才能赚到钱,接下来要么花更多的钱赚更多的利润,要么利用当前已经赚到的钱赚更多的利润。
  2. 否则,当前进入i是亏钱的,我们递归处理i的每个儿子,这样就知道了每个儿子最少需要多少钱才能赚到钱。那我们肯定优先需要的钱少的儿子去赚钱。用数据结构《堆》来维护可以进入赚钱的儿子。直到当前从亏钱变成赚钱。此时i的堆里面还有一些儿子没有走。
  3. 在第2步中的细节:当我们在一个儿子v中赚完钱之后,把v从i的堆中删除,并且把v的堆(也就是v没赚完钱的儿子)和i的堆合并,因为i赚了v之后,v的还没赚钱的儿子都可以被i访问了。
    最后节点0赚钱所需要的最小花费就是答案。

代码:
代码中堆的合并用可并堆实现,总体时间 O ( n l o g n ) O(nlogn) O(nlogn)。当然也可以尝试启发式合并 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
using namespace std;
const int maxn = 2e5 + 50;
int ch[maxn*20][2], cost[maxn*20], id[maxn*20], dis[maxn*20], tot = 0;
int new_node(int a, int b){
    ++tot;
    cost[tot] = a;
    id[tot] = b;
    dis[tot] = ch[tot][0] = ch[tot][1] = 0;
    return tot;
}
int merge(int x,int y)
{
    if(!x) return y;
    if(!y) return x;
    if(cost[x] > cost[y]) swap(x,y);
    ch[x][1]=merge(ch[x][1],y);
    if(dis[ch[x][1]]>dis[ch[x][0]])
        swap(ch[x][1],ch[x][0]);
    dis[x]=dis[ch[x][1]]+1;
    return x;
}
int n;
int x[maxn], y[maxn];
vector<int> g[maxn];
const int inf = 0x3f3f3f3f;
void init(){
    scanf("%d", &n);
    fors(i,1,n){
        int p; scanf("%d%d%d", &p, &x[i], &y[i]);
        if(x[i] == -1) x[i] = inf;
        g[p].pb(i);
    }
}
int bad[maxn];
void dfs_bad(int u){
    bad[u] = y[u]-1;
    int sum = y[u] - x[u];
    for(int v: g[u]){
        dfs_bad(v);
        sum += bad[v];
    }
    bad[u] = max(sum, bad[u]);
    return;
}
vector<int> res[maxn];
int c[maxn], p[maxn];
#define P pair<int, int>
// cost   id
const int lim = 5e8;
int cnt = 0;
int root[maxn];
void dfs(int u){
    c[u] = y[u];
    p[u] = x[u] - y[u];
    root[u] = 0;
    for(int v: g[u]){
        dfs(v);
        if(p[v] > 0) root[u] = merge(root[u], new_node(c[v], v) );
    }
    while(p[u] <= 0  && root[u]){
            int v = id[root[u]];
            root[u] = merge(ch[root[u]][0], ch[root[u]][1]);
            if(c[u] + p[u] >= c[v]){
                p[u] += p[v];
            }else{
                c[u] += c[v]-(p[u] + c[u]);
                p[u] += p[v];
            }
            root[u] = merge(root[u], root[v]);
    }
    return;
}
void sol(){
    dfs_bad(0);//bad[0]+1 is worst ans
    dfs(0);
    cout<<bad[0]+1<<" "<<c[0]<<endl;
}
int main()
{
    init();
    sol();
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值