題解 「BZOJ4919 Lydsy1706月賽」大根堆
阿新 • • 發佈:2020-11-04
題目大意
給出一個 \(n\) 個點的樹,每個點有權值,從中選出一些點,使得滿足大根堆的性質。(即一個點的祖先節點如果選了那麼該點的祖先節點的權值一定需要大於該點權值)
問能選出來的大根堆的最大大小。\(n\le 2\times 10^5\)
線段樹合併
跟堯姐一起想的。
首先不難想到 dp,我們可以設 \(f_{u,i}\) 表示 \(u\) 子樹內選出頂點權值 \(\le i\) 的大根堆的最大大小。我們可以列出轉移式:
\[f_{u,i}=\max\{\sum_{v\in son_u} f_{v,i},\sum_{v\in son_u} f_{v,i-1}+1\} \]然後我們發現這個東西似乎可以線段樹合併轉移。具體來說我們每次先把兒子的線段樹合併起來,然後計算以當前節點為根的大根堆的最大大小。
不難發現,我們需要實現區間取 \(\max\),單點查詢。這個東西如果要硬上的話似乎是不好搞的,因為線段樹合併似乎不支援懶標記(此處存疑)。
接著思考,可以發現的是,我們每次修改的一定是一段區間,而且如果能夠修改肯定是區間 \(+1\)。於是,我們就可以維護差分陣列,然後線上段樹上二分找到修改的端點即可。
時間複雜度 \(\Theta(n\log n)\),空間複雜度 \(\Theta(n\log n)\)。
\(\texttt{Code}\)
#include <bits/stdc++.h> using namespace std; #define Int register int #define INF 0x7f7f7f7f #define MAXN 200005 template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;} template <typename T,typename ... Args> inline void read (T &t,Args&... args){read (t);read (args...);} template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');} vector <int> G[MAXN]; int n,uni,res,f[MAXN],rt[MAXN],tmp[MAXN],value[MAXN]; struct Segment{ #define MAXM MAXN*100 int cnt,son[MAXM][2],sum[MAXM]; void modify (int &x,int l,int r,int pos,int val){ if (!x) x = ++ cnt;sum[x] += val; if (l == r) return ; int mid = (l + r) >> 1; if (pos <= mid) modify (son[x][0],l,mid,pos,val); else modify (son[x][1],mid + 1,r,pos,val); } int query (int x,int l,int r,int ql,int qr){ if (!x) return 0; if (ql > qr) return 0; if (l >= ql && r <= qr) return sum[x]; int mid = (l + r) >> 1,res = 0; if (ql <= mid) res += query (son[x][0],l,mid,ql,qr); if (qr > mid) res += query (son[x][1],mid + 1,r,ql,qr); return res; } int Merge (int x,int y){ if (!x || !y) return x + y; sum[x] += sum[y]; son[x][0] = Merge (son[x][0],son[y][0]); son[x][1] = Merge (son[x][1],son[y][1]); return x; } int find (int x,int l,int r,int t){ if (!x) return r; if (l == r) return l; int mid = (l + r) >> 1; if (t < sum[son[x][0]]) return find (son[x][0],l,mid,t); else return find (son[x][1],mid + 1,r,t - sum[son[x][0]]); } }Tree; void dfs (int u){ f[u] = 1; for (Int v : G[u]){ dfs (v); rt[u] = Tree.Merge (rt[u],rt[v]); } f[u] = Tree.query (rt[u],1,uni,1,value[u] - 1) + 1; Tree.modify (rt[u],1,uni,value[u],1); int pos = Tree.find (rt[u],1,uni,f[u]); if (Tree.query (rt[u],1,uni,1,pos) > f[u]) -- pos; if (pos < uni) Tree.modify (rt[u],1,uni,pos + 1,-1); } signed main(){ read (n); for (Int i = 1,fa;i <= n;++ i) read (value[i],fa),G[fa].push_back (i),tmp[i] = value[i]; sort (tmp + 1,tmp + n + 1),uni = unique (tmp + 1,tmp + n + 1) - tmp - 1; for (Int i = 1;i <= n;++ i) value[i] = lower_bound (tmp + 1,tmp + uni + 1,value[i]) - tmp; dfs (1);write (Tree.query (rt[1],1,uni,1,uni)),putchar ('\n'); return 0; }
set 啟發式合併
by @自為風月馬前卒
本質上來說是個貪心???
可以發現的是,在大根堆大小相同的時候,我們肯定想要頂點的權值儘可能小,這樣就對後面合併更優。
於是,我們可以考慮把當前點與子樹進行合併,我們如果子樹內有比當前點更大的值,我們發現答案不會變,直接替換 set 中比它與它相鄰的樹即可。否則直接加進來即可。
時間複雜度 \(\Theta(n\log^2n)\),空間複雜度 \(\Theta(n)\)。
\(\texttt{Code}\)
#include<bits/stdc++.h> #define sit multiset<int>::iterator using namespace std; const int MAXN = 2e5 + 10; inline int read() { char c = getchar(); int x = 0, f = 1; while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();} while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); return x * f; } vector<int> v[MAXN]; multiset<int> s[MAXN]; int N, val[MAXN]; void dfs(int x, int fa) { for(int i = 0; i < v[x].size(); i++) { int to = v[x][i]; if(to == fa) continue; dfs(to, x); if(s[to].size() > s[x].size()) swap(s[to], s[x]); for(sit it = s[to].begin(); it != s[to].end(); it++) s[x].insert(*it); s[to].clear(); } sit it = s[x].lower_bound(val[x]); if(it != s[x].end()) s[x].erase(it); s[x].insert(val[x]); } main() { N = read(); for(int i = 1; i <= N; i++) { val[i] = read(); int x = read(); v[i].push_back(x); v[x].push_back(i); } dfs(1, 0); printf("%d", s[1].size()); return 0; }