• 「APIO2012」「Luogu P1552」派遣


    Description

    在这个帮派里,有一名忍者被称之为 Master。除了 Master 以外,每名忍者都有且仅有一个上级。为保密,同时增强忍者们的领导力,所有与他们工作相关的指令总是由上级发送给他的直接下属,而不允许通过其他的方式发送。

    现在你要招募一批忍者,并把它们派遣给顾客。你需要为每个被派遣的忍者支付一定的薪水,同时使得支付的薪水总额不超过你的预算。另外,为了发送指令,你需要选择一名忍者作为管理者,要求这个管理者可以向所有被派遣的忍者发送指令,在发送指令时,任何忍者(不管是否被派遣)都可以作为消息的传递人。管理者自己可以被派遣,也可以不被派遣。当然,如果管理者没有被排遣,你就不需要支付管理者的薪水。

    你的目标是在预算内使顾客的满意度最大。这里定义顾客的满意度为派遣的忍者总数乘以管理者的领导力水平,其中每个忍者的领导力水平也是一定的。

    写一个程序,给定每一个忍者 (i) 的上级 (B_i),薪水 (C_i),领导力 (L_i),以及支付给忍者们的薪水总预算 (M),输出在预算内满足上述要求时顾客满意度的最大值。

    Hint

    • (1 ≤ n ≤ 10^5) 忍者的个数;
    • (1 ≤ m ≤ 10^9) 薪水总预算;
    • (0 ≤ B_i < i) 忍者的上级的编号;
    • (1 ≤ C_i ≤ M) 忍者的薪水;
    • (1 ≤ L_i ≤ 10^9) 忍者的领导力水平。
    • 对于前 (30\%) 的数据,(n ≤ 3 imes 10^3)

    Solution

    很显然忍者的工作关系构成了一颗树形结构。

    那么问题可以形式化为:求每个子树中,选取尽可能多的结点,使这些结点的总花费不超过 (m)。最后合并所有子树的答案。

    不难想到从叶结点开始做起,并用合适的数据结构维护忍者的集合。

    每当做到一个点,先将当前的集合 合并 到上级那去,同时调整上级的集合,使得其中的忍者的花费不超过 (m) 且集合尽可能大。

    我们贪心地想,只要每次弹出花费大的忍者即可,因为答案只涉及树根的领导力以及忍者的个数。

    一个点做好后,直接用上级去更新答案即可。

    对于集合的维护,我们需要一个合适的数据结构,支持删除最大值和高效合并。这里选择左偏树。

    关于复杂度,由于每个忍者都只会弹出一次,因此时间复杂度为 (O(nlog n))

    Code

    /*
     * Author : _Wallace_
     * Source : https://www.cnblogs.com/-Wallace-/
     * Problem : APIO2012 Luogu P1552 派遣
     */
    #include <algorithm>
    #include <iostream>
    
    using namespace std;
    const int N = 1e5 + 5;
    
    struct ninja {
    	int fa, cost, lead;
    } nj[N];
    int n, m;
    
    struct ltNode {
    	int ch[2], val, dist, rt;
    } t[N];
    #define lc t[x].ch[0]
    #define rc t[x].ch[1]
    
    int merge(int x, int y) {
    	if (!x || !y) return x | y;
    	if (t[x].val < t[y].val) swap(x, y);
    	rc = merge(rc, y);
    	if (t[lc].dist < t[rc].dist) swap(lc, rc);
    	t[x].rt = t[lc].rt = t[rc].rt = x;
    	t[x].dist = t[rc].dist + 1;
    	return x;
    }
    int findrt(int x) {
    	return x == t[x].rt ? x : t[x].rt = findrt(t[x].rt);
    }
    
    struct LefTr {
    	int root;
    	int size;
    	long long sum;
    	inline void join(LefTr t) {
    		size += t.size, sum += t.sum;
    		root = merge(root, t.root);
    	}
    	inline void pop() {
    		sum -= t[root].val;
    		root = merge(t[root].ch[0], t[root].ch[1]);
    		--size;
    	}
    } lt[N];
    
    void init() {
    	for (register int i = 1; i <= n; i++) {
    		lt[i].root = t[i].rt = i;
    		lt[i].size = 1;
    		lt[i].sum = t[i].val = nj[i].cost;
    	}
    }
    
    signed main() {
    	ios::sync_with_stdio(false);
    	cin >> n >> m;
    	for (register int i = 1; i <= n; i++)
    		cin >> nj[i].fa >> nj[i].cost >> nj[i].lead;
    	init();
    	
    	long long ans = 0ll;
    	for (register int i = 1; i <= n; i++)
    		ans = max(ans, nj[i].lead * 1LL);
    	
    	for (register int i = n; i > 1; i--) {
    		int f = nj[i].fa;
    		lt[f].join(lt[i]);
    		while (lt[f].sum > m) lt[f].pop();
    		ans = max(ans, lt[f].size * 1LL * nj[f].lead);
    	}
    	
    	cout << ans << endl;
    	return 0;
    }
    
  • 相关阅读:
    python-多任务-进程
    注解_Annotation
    ZIP压缩输入/输出流
    什么是API,这篇文章让你豁然开朗
    异常处理(在控制台输入数据)
    控件监听与面板监听
    多态与继承
    Java——socketser与cli
    20170307
    20180305
  • 原文地址:https://www.cnblogs.com/-Wallace-/p/13222760.html
Copyright © 2020-2023  润新知