大根堆/领导集团问题

首先,这肯定是个 \(dp\) ,我们设 \(dp_{u,i}\) 表示在 \(u\) 节点的子树中,选出的最大值为 \(i\) 时的最多点数,则有,

\[dp_{u,i}=\max\left(\sum_{v\in son_{u}}dp_{v,i},\sum_{v\in son_{u}}dp_{v,i-1}+1\right) \]

我们发现,对于i来说,这个 \(dp\) 值是单调的然后你就可以用二分,所以我们尝试去维护一个差分序列

Code
#include<cstdio>
#include<algorithm>
#define MAX 200001
#define re register
namespace OMA
{
   int n,len;
   struct Graph
   {
     int next;
     int to;
   }edge[MAX];
   int root[MAX];
   int val[MAX],v[MAX];
   int cnt=1,head[MAX];
   struct Segment
   {
     int tot;  
     int cnt[MAX<<5];
     int ls[MAX<<5],rs[MAX<<5];
     inline void insert(int &p,int l,int r,int x)
     {
       cnt[p = (!p)?++tot:p]++;
       if(l==r)
       { return ; }
       int mid = (l+r)>>1;
       if(x<=mid)
       { insert(ls[p],l,mid,x); }
       if(x>mid)
       { insert(rs[p],mid+1,r,x); }
     }
     inline int merge(int p1,int p2)
     {
       if(!p1||!p2)
       { return p1|p2; }
       cnt[p1] += cnt[p2];
       ls[p1] = merge(ls[p1],ls[p2]);
       rs[p1] = merge(rs[p1],rs[p2]);
       //cnt[p1] = cnt[ls[p1]]+cnt[rs[p1]];
       return p1;
     }
     inline bool change(int p,int l,int r,int x)
     {
       if(!cnt[p])
       { return false; }
       if(l==r)
       { cnt[p]-- ; return true; }
       int mid = (l+r)>>1;
       if(x<=mid&&change(ls[p],l,mid,x))
       { cnt[p]--; return true; }
       if(change(rs[p],mid+1,r,x))
       { cnt[p]--; return true; }
       return false;
     }
     inline void dfs(int u)
     {
       for(re int i=head[u]; i; i=edge[i].next)
       { int v = edge[i].to; dfs(v); root[u] = merge(root[u],root[v]); }
       change(root[u],1,len,val[u]),insert(root[u],1,len,val[u]);
     }
   }Tree;
   inline int read()
   {
     int s=0,w=1; char ch=getchar();
     while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
     while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
     return s*w;
   }
   inline void add(int u,int v)
   {
     edge[++cnt].next = head[u];
     edge[cnt].to = v;
     head[u] = cnt;
   }
   signed main()
   {
     n = read();
     for(re int i=1; i<=n; i++)
     { val[i] = v[i] = /*-*/read(); add(read(),i); }
     //for(re int i=2; i<=n; i++)
     //{ add(read(),i); }
     std::sort(v+1,v+1+n);
     len = std::unique(v+1,v+1+n)-v-1;
     for(re int i=1; i<=n; i++)
     { val[i] = std::lower_bound(v+1,v+1+len,val[i])-v; }
     Tree.dfs(1);
     printf("%d\n",Tree.cnt[root[1]]);
     return 0;
   }
}
signed main()
{ return OMA::main(); }

然后,下面是一些调试的时候的问题:
线段树合并写锅了,之前一直写的是下边这个

inline int merge(int p1,int p2)
{  
   if(!p1||!p2)
   { return p1|p2; }
   ls[p1] = merge(ls[p1],ls[p2]);
   rs[p1] = merge(rs[p1],rs[p2]);
   cnt[p1] = cnt[ls[p1]]+cnt[rs[p1]];
   return p1;
}

改成下面这个,就A掉了

inline int merge(int p1,int p2)
{  
   if(!p1||!p2)
   { return p1|p2; }
   cnt[p1] += cnt[p2];
   ls[p1] = merge(ls[p1],ls[p2]);
   rs[p1] = merge(rs[p1],rs[p2]);
   return p1;
}

好像就我这个nt一开始这么写

其实也很好理解,如果是按第一个那么来写,是在合并后转移,那么考虑如果该节点是叶子节点,其左右儿子都为0,那么再按第一个那样转移的话,其维护的值就成了0,但实际上,它是有值的,所以就错了,而第二种是在未转移之前就转移,这样则能避免上述情况的发生。

但是,两种合并方法什么时候用,还要具体问题具体分析,不能盲目的直接套板子,千万不能成为板子选手,那样就没有了意义。

或者说,是这道题数据的原因
如果有懂的,麻烦教教我这个菜鸡QAQ

Updated on 2021-08-08

考试题有道线段树合并,再次因为merge的问题出锅,在WYZG的提醒下,我又回来补一下锅。

为什么第一个写法出锅?,还是上边所说的叶子节点的问题,如何把第一个merge也改对? 只需判断一下当前是否递归叶子节点即可,然后记得合并一下二者信息。

关于合并,具体问题一定要去分析,有些题目叶子合并时信息更新的要求也不一样,视具体题目而定,不可直接冲板子。

     inline int merge(int p1,int p2,int l,int r)
     {
       if(!p1||!p2)
       { return p1|p2; }
       if(l==r)
       { cnt[p1] += cnt[p2]; return p1; }
       //cnt[p1] += cnt[p2];
       int mid = (l+r)>>1;
       ls[p1] = merge(ls[p1],ls[p2],l,mid);
       rs[p1] = merge(rs[p1],rs[p2],mid+1,r);
       cnt[p1] = cnt[ls[p1]]+cnt[rs[p1]];
       return p1;
     }

你也可以用set启发式合并但好像这个方法适用面不是很广
直接用 \(multiset\) 维护 \(LIS\) 即可。
将小的 \(multiset\) 向大的 \(multiset\) 合并。
然后模拟 \(dp\) 的过程,即找到比当前 \(val_{u}\) 大的那个节点,并将其删掉。然后再将自己插入即可。
答案即为 根节点1的 \(size\) 大小。
时间复杂度 \(\mathcal O(n\log^{2} n)\) \(n\) 为元素的个数 \(O2\) 跑得飞快
输入的时候将 \(val\) 都加个负号即可A掉领导集团问题

Code
#include<set>
#include<cstdio>
#define MAX 200001
#define re register
namespace OMA
{
   int n;
   struct Graph
   {
     int next;
     int to;
   }edge[MAX];
   int val[MAX];
   int cnt=1,head[MAX];
   std::multiset<int> s[MAX];
   std::multiset<int>::iterator it;
   inline int read()
   {
     int s=0,w=1; char ch=getchar();
     while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
     while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
     return s*w;
   }
   inline void add(int u,int v)
   {
     edge[++cnt].next = head[u];
     edge[cnt].to = v;
     head[u] = cnt;
   }
   inline void dfs(int u,int fa)
   {
     for(re int i=head[u]; i; i=edge[i].next)
     {
       int v = edge[i].to;
       if(v!=fa)
       {
         dfs(v,u);
         if(s[v].size()>s[u].size())
         { std::swap(s[u],s[v]); }
         for(it=s[v].begin(); it!=s[v].end(); it++)
         { s[u].insert(*it); }
         s[v].clear();
       }
     }
     it = s[u].lower_bound(val[u]);
     if(it!=s[u].end())
     { s[u].erase(it); }
     s[u].insert(val[u]);
   }
   signed main()
   {
     n = read();
     for(re int i=1; i<=n; i++)
     { val[i] = read(); add(read(),i); }
     dfs(1,0);
     printf("%d\n",s[1].size());
     return 0;
   }
}
signed main()
{ return OMA::main(); }
posted @ 2021-06-13 17:53  -OMA-  阅读(95)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end