1. 程式人生 > >bzoj4919 大根堆 [啟發式合併]

bzoj4919 大根堆 [啟發式合併]

Description:
一棵樹,每個點u有權值val[u],要求選出最多的點,並且滿足每個被選的點子樹中沒有權值大於等於該點權值。

Solution:
吐槽:
考試怒剛t2,結果沒調出來,看到t3覺得是線段樹合併之類的題,感覺寫不出來。考試後也寫了一個線段樹合併,由於第一次寫所以調了很長時間沒調出來,於是寫了這個set版本。

本質上是求樹上lis,於是我們考慮lis中維護的陣列a,a[i]表示長度為ilis結尾的最小值,用set維護。每次啟發式合併set,並且用當前點的值替換第一個大於等於該值的值,最後輸出根節點的

size即可。
set元素的相對位置也就是lis陣列的下標,那麼size也就是最長的dp值。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int n;
int val[maxn];
vector<int> G[maxn];
multiset<int> s[maxn];
multiset<int> :: iterator it;
void merge(int u, int v) {
    if(s[u].size() < s[v].size()) {
        swap(s[u], s[v]);
    }
    for
(it = s[v].begin(); it != s[v].end(); ++it) { s[u].insert(*it); } s[v].clear(); } void dfs(int u) { for(int i = 0; i < G[u].size(); ++i) { int v = G[u][i]; dfs(v); merge(u, v); } it = s[u].lower_bound(val[u]); if(it != s[u].end()) { s[u].erase(it); } s[u].insert(val[u]); } int
main() { scanf("%d", &n); for(int i = 1; i <= n; ++i) { int x; scanf("%d%d", &val[i], &x); if(x) { G[x].push_back(i); } } dfs(1); printf("%d\n", s[1].size()); return 0; }