bzoj 4919: [Lydsy1706月賽]大根堆 set啟發式合併
阿新 • • 發佈:2019-02-08
題意
給定一棵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;
}