大根堆/领导集团问题
首先,这肯定是个 \(dp\) ,我们设 \(dp_{u,i}\) 表示在 \(u\) 节点的子树中,选出的最大值为 \(i\) 时的最多点数,则有,
我们发现,对于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(); }