题解 P5290 【[十二省联考2019]春节十二响】
龙之吻—水货
2019-04-10 13:35:32
# [十二省联考2019]春节十二响
## 题目大意
给定一棵树,每次选取树上的一个点集,要求点集中的每个点不能是另一个点的祖先,选出点集的代价为点集中权值最大点的权值,问将所有点都选一遍的最小代价为多少。
## 解题报告
似乎其他题解都是将整棵树最后合并成为一个链统计答案的,不过在考场上我并没有到这种方法,而是使用了一种贪心的方法。
尽管到现在我都无法证明它的正确性,但是在它的确可以通过所有的测试点,在考场上也帮我得到了 $100$ 分的,那么就暂且相信它是正确的吧 QwQ
首先,我们将所有点按从大到小的顺序进行排序,然后我们枚举每一个点,如果当前点被选过了的话就跳过。之后,我们将这点点放入一个新的点集中。然后,我们从大到小枚举这个点之后的点,能加则加,不能加就跳过,放一个伪代码就是 :
```
sort i in val
for i 1 to n :
if i is used :
continue
make a new set
put i into the set
for j i + 1 to n :
if j is used :
continue
if j can't be put into set :
continue
put j into set
j is used
```
那么我们现在所需要的就是判断一个点能否被放在点集中,以及找出可选点中最大的点。
显然,我们每选择一个点之后,这个点到根的路径上所有的点,以及这个点为根的子树里面所有的点都不能被选择了。
我们发现,我们现在需要一个支持在树上修改子树,修改链,查询全树最大值的数据结构,而树链剖分可以完美地解决这个问题。
具体来说就是,在线段树上的每个节点维护这段区间是否可选,以及这段区间的最大值。每向集合中加入一个点之后,先将这个点的删除,然后将这个点到根的路径上,以及这个点为根的子树打上一个不可选标记,之后每次从线段树中取出那个最大值即可。当我们选完一个集合之后,要注意把之前的不可选标记清除掉,具体实现可以看代码。
由于每个点只会被处理一次,所以总的时间复杂度为 $O(n\log^2n)$ ,虽然看起来 $2 \times 10^5$ 有点悬,但事实证明出题人并没有特意去卡这个算法,所以就可以 $AC$ 啦!
```cpp
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
class SegmentTree{
private :
static const int maxn = 2e5 + 7;
struct Node{
int max, del;
Node *child[2];
Node() : max(0), del(0) {
child[0] = child[1] = NULL;
}
};
int n, *val;
Node *root, pool[maxn << 2], *tp;
Node *newNode() {
*++tp = Node();
return tp;
}
void update(Node *now) {
now->max = 0;
for (register int i = 0; i < 2; i++) {
if (now->child[i]->del) {
continue;
}
if (val[now->child[i]->max] > val[now->max]) {
now->max = now->child[i]->max;
}
}
}
void buildTree(Node *now, int left, int right) {
if (left == right) {
now->max = left;
return;
}
int mid = (left + right) >> 1;
buildTree(now->child[0] = newNode(), left, mid);
buildTree(now->child[1] = newNode(), mid + 1, right);
update(now);
}
void delMax(Node *now, int left, int right, int l, int r) {
if (left >= l && right <= r) {
now->del = 1;
return;
}
int mid = (left + right) >> 1;
if (r <= mid) {
delMax(now->child[0], left, mid, l, r);
} else if (l > mid) {
delMax(now->child[1], mid + 1, right, l, r);
} else {
delMax(now->child[0], left, mid, l, r);
delMax(now->child[1], mid + 1, right, l, r);
}
update(now);
}
void addMax(Node *now, int left, int right, int l, int r) {
if (left >= l && right <= r) {
now->del = 0;
return;
}
int mid = (left + right) >> 1;
if (r <= mid) {
addMax(now->child[0], left, mid, l, r);
} else if (l > mid) {
addMax(now->child[1], mid + 1, right, l, r);
} else {
addMax(now->child[0], left, mid, l, r);
addMax(now->child[1], mid + 1, right, l, r);
}
update(now);
}
void eraseMax(Node *now, int left, int right, int pos) {
if (left == right) {
now->max = 0;
return;
}
int mid = (left + right) >> 1;
if (pos <= mid) {
eraseMax(now->child[0], left, mid, pos);
} else {
eraseMax(now->child[1], mid + 1, right, pos);
}
update(now);
}
public :
void init(int x, int *m) {
n = x, val = m, tp = pool;
buildTree(root = newNode(), 1, n);
}
void delMax(int l, int r) {
//printf("del : %d %d\n", l, r);
delMax(root, 1, n, l, r);
}
void addMax(int l, int r) {
//printf("add : %d %d\n", l, r);
addMax(root, 1, n, l, r);
}
void eraseMax(int pos) {
eraseMax(root, 1, n, pos);
}
int queryMax() {
//printf("%d %d\n", root->del, root->max);
return root->del ? 0 : root->max;
}
};
const int maxn = 2e5 + 7;
int m[maxn];
class Solution{
private :
typedef long long ll;
typedef std::pair<int, int> par;
int n, id[maxn], fa[maxn], size[maxn], son[maxn], top[maxn];
int h[maxn], cnt, dfn[maxn], red[maxn], end[maxn], val[maxn];
ll ans;
bool used[maxn], have[maxn];
std::vector<int> e[maxn];
std::queue<int> q;
SegmentTree tree;
static bool cmp(int a, int b) {
return m[a] > m[b];
}
void DFS1(int now) {
h[now] = h[fa[now]] + 1;
size[now] = 1;
for (auto v : e[now]) {
DFS1(v);
size[now] += size[v];
if (size[v] > size[son[now]]) {
son[now] = v;
}
}
}
void DFS2(int now, int tp) {
red[end[now] = dfn[now] = ++cnt] = now;
val[cnt] = m[now];
top[now] = tp;
if (son[now]) {
DFS2(son[now], tp);
end[now] = end[son[now]];
}
for (auto v : e[now]) {
if (v == son[now]) {
continue;
}
DFS2(v, v);
end[now] = end[v];
}
//printf("%d : %d %d\n", now, dfn[now], end[now]);
}
inline void mark(int now) {
q.push(now);
tree.eraseMax(dfn[now]);
for (register int i = now; i && !have[i]; i = fa[top[i]]) {
tree.delMax(dfn[top[i]], dfn[i]);
}
tree.delMax(dfn[now], end[now]);
//DFS(now);
}
inline void delMark(int now) {
for (register int i = now; i && !have[i]; i = fa[top[i]]) {
tree.addMax(dfn[top[i]], dfn[i]);
}
tree.addMax(dfn[now], end[now]);
}
void DFS(int now) {
have[now] = 1;
for (auto v : e[now]) {
DFS(v);
}
}
public :
Solution() {
get();
solve();
}
void get() {
scanf("%d", &n);
for (register int i = 1; i <= n; i++) {
scanf("%d", m + i);
id[i] = i;
//pq.push(std::make_pair(m[i], i));
}
for (register int i = 2; i <= n; i++) {
scanf("%d", fa + i);
e[fa[i]].push_back(i);
}
}
void solve() {
DFS1(1);
DFS2(1, 1);
//printf("%d\n", dfn[11]);
tree.init(n, val);
//printf("%d %d\n", dfn[1], end[1]);
for (register int now = tree.queryMax(); now; now = tree.queryMax()) {
now = red[now];
//printf("%d : ", now);
ans += m[now];
mark(now);
for (register int p = tree.queryMax(); p; p = tree.queryMax()) {
p = red[p];
//printf("%d ", p);
mark(p);
}
while (!q.empty()) {
delMark(q.front());
q.pop();
}
//putchar('\n');
}
printf("%lld\n", ans);
}
};
Solution sol;
int main() {}
```