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;
}