1. 程式人生 > >bzoj 4919: [Lydsy1706月賽]大根堆 set啟發式合併

bzoj 4919: [Lydsy1706月賽]大根堆 set啟發式合併

題意

給定一棵n個節點的有根樹,編號依次為1到n,其中1號點為根節點。每個點有一個權值v_i。
你需要將這棵樹轉化成一個大根堆。確切地說,你需要選擇儘可能多的節點,滿足大根堆的性質:對於任意兩個點i,j,如果i在樹上是j的祖先,那麼v_i>v_j。
請計算可選的最多的點數,注意這些點不必形成這棵樹的一個連通子樹。
1<=n<=200000

分析

比賽的時候打了splay啟發式合併,硬生生調了2h,最後還fst了。。。
題解的做法比較巧妙,如果給的圖是序列的話,顯然要求的就是lis。
現在拓展到樹上,仍然可以對每個節點維護一個單調不降的序列表示lis的節點數為i時最大值的最小值。
由於子樹之間互不干擾所以可以直接把兩個序列歸併,然後加入當前當前節點時,把最小的不小於當前點權值的位置替換成當前點權值即可。
可以直接用multiset啟發式合併來實現。

程式碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<set>
using namespace std;

const int N=200005;

int n,val[N],cnt,last[N];
struct edge{int to,next;}e[N];
multiset<int> se[N];
multiset<int>::iterator it;

int
read() { int x=0,f=1;char ch=getchar(); while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } void addedge(int u,int v) { e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt; } void dfs(int x) { for
(int i=last[x],to=e[i].to;i;i=e[i].next,to=e[i].to) { dfs(e[i].to); if (se[x].size()<se[to].size()) swap(se[x],se[to]); for (it=se[to].begin();it!=se[to].end();it++) se[x].insert(*it); se[to].clear(); } it=se[x].lower_bound(val[x]); if (it!=se[x].end()) se[x].erase(it); se[x].insert(val[x]); } int main() { n=read(); for (int i=1;i<=n;i++) { val[i]=read();int x=read(); if (x) addedge(x,i); } dfs(1); printf("%d",se[1].size()); return 0; }