1. 程式人生 > >[bzoj4919]大根堆

[bzoj4919]大根堆

long long i+1 註意 可選 strong 線段樹 ise tro pre

https://www.zybuluo.com/ysner/note/1248857

題面

給定一棵\(n\)個節點的有根樹,編號依次為\(1\)\(n\),其中\(1\)號點為根節點。每個點有一個權值\(v_i\)
選擇盡可能多的節點,使對於任意兩個點\(i,j\),如果\(i\)在樹上是\(j\)的祖先,那麽\(v_i>v_j\)
請計算可選的最多的點數,註意這些點不必形成這棵樹的一個連通子樹。

  • \(n\leq2*10^5\)

    解析

    這題無非想讓選出的這些點自下往上都能構成最長上升子序列
    於是自下往上每條鏈都維護一下最長上升子序列。
    如果兩鏈相交,把兩條序列直接合並(因不影響答案)。
    然後嘗試把交點(\(LCA\)

    )加入序列即可。

關鍵是怎麽維護這東西。
線段樹合並???怒碼\(3KB\)???
\(set\)神器拯救一切。
而且由於合並時兩鏈(樹)互不影響,可以啟發式合並。(如果父親已有序列長度小於兒子,可以父子完全交換)。
復雜度\(O(nlog^2n)\)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<set>
#define re register
#define il inline
#define ll long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int mod=1e9+7,N=2e5+100,M=3000;
struct Edge{int to,nxt;}e[N<<1];
int n,h[N],cnt,w[N];
multiset<int>f[N];
il void add(re int u,re int v)
{
  e[++cnt]=(Edge){v,h[u]};h[u]=cnt;
  e[++cnt]=(Edge){u,h[v]};h[v]=cnt;
}
il ll gi()
{
   re ll x=0,t=1;
   re char ch=getchar();
   while(ch!=‘-‘&&(ch<‘0‘||ch>‘9‘)) ch=getchar();
   if(ch==‘-‘) t=-1,ch=getchar();
   while(ch>=‘0‘&&ch<=‘9‘) x=x*10+ch-48,ch=getchar();
   return x*t;
}
il void dfs(re int u,re int fa)
{
  for(re int i=h[u];i+1;i=e[i].nxt)
    {
      re int v=e[i].to;
      if(v==fa) continue;
      dfs(v,u);
      if(f[v].size()>f[u].size()) swap(f[v],f[u]);
      for(set<int>::iterator j=f[v].begin();j!=f[v].end();j++)
        f[u].insert(*j);
      f[v].clear();
    }
  if(f[u].size()>0&&f[u].lower_bound(w[u])!=f[u].end()) f[u].erase(f[u].lower_bound(w[u]));
  f[u].insert(w[u]);
}
int main()
{
  memset(h,-1,sizeof(h));
  n=gi();
  fp(i,1,n) w[i]=gi(),add(i,gi());
  dfs(1,0);
  printf("%lld\n",1ll*f[1].size());
  return 0;
}

[bzoj4919]大根堆