「解题报告」P9168 [省选联考 2023] 人员调度
很套路的题啊!见过两遍的套路还是不会做啊!很套路的题啊!见过两遍的套路还是不会做啊!很套路的题啊!见过两遍的套路还是不会做啊!很套路的题啊!见过两遍的套路还是不会做啊!很套路的题啊!见过两遍的套路还是不会做啊!很套路的题啊!见过两遍的套路还是不会做啊!很套路的题啊!见过两遍的套路还是不会做啊!很套路的题啊!见过两遍的套路还是不会做啊!很套路的题啊!见过两遍的套路还是不会做啊!很套路的题啊!见过两遍的套路还是不会做啊!很套路的题啊!见过两遍的套路还是不会做啊!
第三遍见这个套路了!
问题中的“最大值”的限制很烦人,实际上由于我们本身要求的是最大化,那么我们可以将最大值的限制直接去掉,改为每个员工可以选择子树内的一个点,或者不选,求权值最大值。显然这个问题与原问题是等价的。
而这个问题就是一个最大权二分图匹配问题了。这是个很经典的问题,可以通过拟阵 / 模拟费用流证明,贪心地选较大的权值一定可以选出最优方案。那么我们只需要选出一个尽可能大的点集,满足所有点都能被匹配上,最大化权值和。
根据 Hall 定理,我们只需要满足第 \(k\) 个子树内的选择的点不超过 \(siz_k\) 个即可。(具体考虑左部点形成的集合对应着若干个子树,而对于右部的每种子树选择方案,左部点肯定要把所有包含在这个子树内的点全部选上,于是就是每个子树内的选取数量不超过 \(siz_k\) 个)
那么我们就有了一个贪心的做法:维护出每个点的子树内还能够选多少点,每次加入一个点,然后这个点到根的路径上均 \(-1\)。如果出现了 \(-1\),那么说明这些点的条件不能被满足,那么找出其中深度最大的点,再将这个点中的权值最小的点删去即可满足所有点的条件。
删除处理很显然,上线段树分治即可。最后复杂度 \(O(n \log^3 n)\),如果上全局平衡二叉树可以降到 \(O(n \log^2 n)\),不过应该没有必要。
代码不难写,然而我考场上还是不会做就是了。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200005;
int n, k, m, t;
int x[MAXN], v[MAXN];
int beg[MAXN], ed[MAXN];
struct Graph {
vector<int> e[MAXN];
void add(int u, int v) {
e[u].push_back(v), e[v].push_back(u);
}
int siz[MAXN], son[MAXN], dep[MAXN], fa[MAXN];
void dfs1(int u, int pre) {
fa[u] = pre, dep[u] = dep[pre] + 1, siz[u] = 1;
for (int v : e[u]) if (v != pre) {
dfs1(v, u);
if (siz[v] > siz[son[u]]) son[u] = v;
siz[u] += siz[v];
}
}
int dfn[MAXN], idf[MAXN], top[MAXN], dcnt;
int ed[MAXN];
void dfs2(int u, int pre, int t) {
top[u] = t, dfn[u] = ++dcnt, idf[dcnt] = u;
if (son[u]) dfs2(son[u], u, t);
for (int v : e[u]) if (v != pre && v != son[u]) {
dfs2(v, u, v);
}
ed[u] = dcnt;
}
} g;
struct SegmentTree1 { // siz_i
struct Node {
int tag;
pair<int, int> mn;
} t[MAXN << 2];
#define lc (i << 1)
#define rc (i << 1 | 1)
void build(int i = 1, int l = 1, int r = n) {
t[i].tag = 0;
if (l == r) {
t[i].mn = { g.siz[g.idf[l]], -l };
return;
}
int mid = (l + r) >> 1;
build(lc, l, mid), build(rc, mid + 1, r);
t[i].mn = min(t[lc].mn, t[rc].mn);
}
void pushDown(int i) {
if (t[i].tag) {
t[lc].tag += t[i].tag;
t[rc].tag += t[i].tag;
t[lc].mn.first += t[i].tag;
t[rc].mn.first += t[i].tag;
t[i].tag = 0;
}
}
void add(int a, int b, int v, int i = 1, int l = 1, int r = n) {
if (a <= l && r <= b) {
t[i].tag += v;
t[i].mn.first += v;
return;
}
int mid = (l + r) >> 1;
pushDown(i);
if (a <= mid) add(a, b, v, lc, l, mid);
if (b > mid) add(a, b, v, rc, mid + 1, r);
t[i].mn = min(t[lc].mn, t[rc].mn);
}
pair<int, int> query(int a, int b, int i = 1, int l = 1, int r = n) {
if (a <= l && r <= b) return t[i].mn;
int mid = (l + r) >> 1;
pushDown(i);
if (b <= mid) return query(a, b, lc, l, mid);
if (a > mid) return query(a, b, rc, mid + 1, r);
return min(query(a, b, lc, l, mid), query(a, b, rc, mid + 1, r));
}
void chainAdd(int x, int v) {
while (x) {
add(g.dfn[g.top[x]], g.dfn[x], v);
x = g.fa[g.top[x]];
}
}
pair<int, int> chainQuery(int x) {
pair<int, int> ret = { INT_MAX, 0 };
while (x) {
ret = min(ret, query(g.dfn[g.top[x]], g.dfn[x]));
x = g.fa[g.top[x]];
}
return ret;
}
} st1;
struct SegmentTree2 { // v_i
pair<int, int> t[MAXN << 2];
set<pair<int, int>> s[MAXN];
void build(int i = 1, int l = 1, int r = n) {
t[i] = { INT_MAX, l };
if (l == r) {
s[l].insert({ INT_MAX, 0 });
return;
}
int mid = (l + r) >> 1;
build(lc, l, mid), build(rc, mid + 1, r);
}
pair<int, int> query(int a, int b, int i = 1, int l = 1, int r = n) {
if (a <= l && r <= b) return t[i];
int mid = (l + r) >> 1;
if (b <= mid) return query(a, b, lc, l, mid);
if (a > mid) return query(a, b, rc, mid + 1, r);
return min(query(a, b, lc, l, mid), query(a, b, rc, mid + 1, r));
}
void insert(int d, pair<int, int> v, int i = 1, int l = 1, int r = n) {
if (l == r) {
s[l].insert(v);
t[i] = *s[l].begin();
return;
}
int mid = (l + r) >> 1;
if (d <= mid) insert(d, v, lc, l, mid);
else insert(d, v, rc, mid + 1, r);
t[i] = min(t[lc], t[rc]);
}
void erase(int d, pair<int, int> v, int i = 1, int l = 1, int r = n) {
if (l == r) {
s[l].erase(v);
t[i] = *s[l].begin();
return;
}
int mid = (l + r) >> 1;
if (d <= mid) erase(d, v, lc, l, mid);
else erase(d, v, rc, mid + 1, r);
t[i] = min(t[lc], t[rc]);
}
} st2;
long long ans;
struct SegmentTree3 {
vector<int> t[MAXN << 2];
void add(int a, int b, int v, int i = 1, int l = 0, int r = m) {
if (a <= l && r <= b) {
t[i].push_back(v);
return;
}
int mid = (l + r) >> 1;
if (a <= mid) add(a, b, v, lc, l, mid);
if (b > mid) add(a, b, v, rc, mid + 1, r);
}
void solve(int i = 1, int l = 0, int r = m) {
stack<pair<int, int>> st;
for (int j : t[i]) {
ans += v[j];
st1.chainAdd(x[j], -1);
st2.insert(g.dfn[x[j]], { v[j], j });
st.push({j, 0});
auto res = st1.chainQuery(x[j]);
if (res.first == -1) {
auto p = st2.query(-res.second, g.ed[g.idf[-res.second]]);
ans -= p.first;
int j = p.second;
st1.chainAdd(x[j], 1);
st2.erase(g.dfn[x[j]], { v[j], j });
st.push({j, 1});
}
}
if (l == r) {
printf("%lld ", ans);
} else {
int mid = (l + r) >> 1;
solve(lc, l, mid), solve(rc, mid + 1, r);
}
while (!st.empty()) {
auto p = st.top(); st.pop();
int j = p.first;
if (p.second == 0) {
ans -= v[j];
st1.chainAdd(x[j], 1);
st2.erase(g.dfn[x[j]], { v[j], j });
} else {
ans += v[j];
st1.chainAdd(x[j], -1);
st2.insert(g.dfn[x[j]], { v[j], j });
}
}
}
} st3;
int main() {
scanf("%*d%d%d%d", &n, &k, &m);
for (int i = 2; i <= n; i++) {
int p; scanf("%d", &p);
g.add(p, i);
}
g.dfs1(1, 0), g.dfs2(1, 0, 1);
st1.build(), st2.build();
for (int i = 1; i <= k; i++) {
scanf("%d%d", &x[i], &v[i]);
beg[i] = 0, ed[i] = m;
}
for (int i = 1; i <= m; i++) {
int op; scanf("%d", &op);
if (op == 1) {
++k;
scanf("%d%d", &x[k], &v[k]);
beg[k] = i, ed[k] = m;
} else {
int x; scanf("%d", &x);
ed[x] = i - 1;
}
}
for (int i = 1; i <= k; i++) {
st3.add(beg[i], ed[i], i);
}
st3.solve();
return 0;
}