线段树合并 大根堆

C. 大根堆

内存限制:256 MiB 时间限制:1000 ms 标准输入输出
题目类型:传统 评测方式:文本比较
 

题目描述

给定一棵n个节点的有根树,编号依次为1到n,其中1号点为根节点。每个点有一个权值v_i。 你需要将这棵树转化成一个大根堆。确切地说,你需要选择尽可能多的节点,满足大根堆的性质:对于任意两个点i,j,如果i在树上是j的祖先,那么v_i>v_j。 请计算可选的最多的点数,注意这些点不必形成这棵树的一个连通子树。

输入格式

第一行包含一个正整数n(1<=n<=200000),表示节点的个数。 接下来n行,每行两个整数v_i,p_i(0<=v_i<=10^9,1<=p_i<i,p_1=0),表示每个节点的权值与父亲。

输出格式

输出一行一个正整数,即最多的点数。

样例

样例输入

6
3 0
1 1
2 1
3 1
4 1
5 1

样例输出

5


析:线段树合并题,但是此题的权值线段树存储的是合法的方案数,并不是值域。
显然,应该从叶子节点开始(也就是递归)向上更新值,每个点有选或不选两种可能;
说一下卡住我的几个地方:首先,记录每个点选或不选的方案数,刚开始,我想建两颗树来存,但是打起来比较麻烦,所以,我先将线段树合并,然后再考虑选或不选的问题
若选,最大值为:ans1=query(rt[x],1,cnt,v[x]-1)+1,若不选,最小值为ans2=query(rt[x],1,cnt,v[x]);
所以,若ans1<=ans2,显然可以不选,直接return;
第二个地方就是选择后的更新,通过二分找到一个最大的区间,满足 query(rt[x],1,cnt,mid)<ans1 ,所以,在[v[x],mid]区间的合法方案数+1
注意,因为线段树存储的是方案数,所以不能 pushup,各节点之间独立。
在查询过程中,由于之前的updata, sum的值发生了变化,所以要加上,并且计算左右儿子
最后,注意一个卡了我好久的细节,注意线段树开点的时候的取地址符!!

代码:
#include<bits/stdc++.h>
#define re register int
using namespace std;
const int N=2e5+10;
const int INF=1e8;
int n,tot,cnt,num;
int maxx=-INF;
int v[N],v2[N];
int rt[N];
int to[N<<1],next[N<<1],head[N<<1];
struct CUN
{
    int lc,rc,sum;
}use[N*40];
int merge(int p,int q,int l,int r)
{
    if(!p)
        return q;
    if(!q)
        return p;
    use[p].sum+=use[q].sum;
    int mid=(l+r)>>1;
    use[p].lc=merge(use[p].lc,use[q].lc,l,mid);
    use[p].rc=merge(use[p].rc,use[q].rc,mid+1,r);
    return p;
}
int query(int p,int l,int r,int z)
{
    if(!p)
        return 0;
    int mid=(l+r)>>1;
    int o=use[p].sum;
    if(z<=mid)
        o+=query(use[p].lc,l,mid,z);
    else
        o+=query(use[p].rc,mid+1,r,z);
    return o;
}
void updata(int &p,int L,int R,int l,int r,int z)
{
    if(!p)
        p=++num;
    if(l<=L&&R<=r)
    {
        use[p].sum+=z;
        return;
    }
    int mid=(L+R)>>1;
    if(l<=mid)
        updata(use[p].lc,L,mid,l,r,z);
    if(r>mid)
        updata(use[p].rc,mid+1,R,l,r,z);
}
void dfs(int x)
{
    int ans1=0,ans2=0;
    for(re i=head[x];i;i=next[i])
    {
        int p=to[i];
        dfs(p);
        rt[x]=merge(rt[x],rt[p],1,cnt);
    }
    ans1=query(rt[x],1,cnt,v[x]-1)+1;
    ans2=query(rt[x],1,cnt,v[x]);
    if(ans1<=ans2)
        return;
    int l=v[x],r=cnt,pos=v[x];
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(query(rt[x],1,cnt,mid)<ans1)
        {
            l=mid+1;
            pos=mid;
        }
        else
            r=mid-1;
    }
    updata(rt[x],1,cnt,v[x],pos,1);
}
void add(int x,int y)
{
    to[++tot]=y;
    next[tot]=head[x];
    head[x]=tot;
}
int main()
{
    scanf("%d",&n);
    int a,b;
    for(re i=1;i<=n;i++)
    {
        scanf("%d%d",&a,&b);
        v[i]=a;
        v2[i]=v[i];
        add(b,i);    
    }
    sort(v2+1,v2+n+1);
    cnt=unique(v2+1,v2+n+1)-v2-1;
    for(re i=1;i<=n;i++)
        v[i]=lower_bound(v2+1,v2+cnt+1,v[i])-v2;
    dfs(1);
    printf("%d\n",query(rt[1],1,cnt,cnt));
    return 0;
}


E. 领导集团问题

内存限制:256 MiB 时间限制:1000 ms 标准输入输出
题目类型:传统 评测方式:文本比较

 

题目描述

一个公司的组织领导架构可以用一棵领导树来表示。公司的每个成员对应于树中一个结点

  ,且每个成员都有响应的级别    。越高层的领导,其级别值    越小。树中任何两个结点之间有边相连,则表示与结点相应的两个成员属于同一部门。领导集团问题就是根据公司的领导树确定公司的最大部门。换句话说,也就是在领导树中寻找最大的部门结点子集,使得的结点      ,如果      的子孙结点,则 

编程任务:对于任意对于给定的领导树,计算出领导树中最大的部门结点子集。


输入格式

第一行有一个正整数

 ,表示领导树的结点数。接下来的一行中有   个整数。第   个数表示    。再接下来的     行中,第   行有一个整数    表示   

的双亲结点。


 

 为正整数,    


输出格式

输出找到的最大的部门的成员数。


样例

样例输入

6
2 5 1 3 5 4
1
1
2
2
4

样例输出

4
同上,这道题就是求一个小根堆,我们只需要将上面的操作该一改就好了
#include<bits/stdc++.h>
#define re register int
#define net next
using namespace std;
const int N=2e5+10;
const int INF=1e8;
int n,tot,cnt,num;
int maxx=-INF;
int v[N],v2[N];
int rt[N];
int to[N<<1],next[N<<1],head[N<<1];
struct CUN
{
	int lc,rc,sum; 
}use[N*40];
void add(int x,int y)
{
	to[++tot]=y;
	net[tot]=head[x];
	head[x]=tot;
}
int merge(int p,int q,int l,int r)
{
	if(!p)
		return q;
	if(!q)
		return p;
	use[p].sum+=use[q].sum;
	int mid=(l+r)>>1;
	use[p].lc=merge(use[p].lc,use[q].lc,l,mid);
	use[p].rc=merge(use[p].rc,use[q].rc,mid+1,r);
	return p;
}
int query(int p,int l,int r,int z)
{
	if(!p)
		return 0;
	int mid=(l+r)>>1;
	int o=use[p].sum;
	if(z>mid)
		o+=query(use[p].rc,mid+1,r,z);
	else
		o+=query(use[p].lc,l,mid,z);
	return o;
}
void updata(int &p,int L,int R,int l,int r,int z)
{
	if(!p)
		p=++num;
	if(l<=L&&R<=r)
	{
		use[p].sum+=z;
		return;
	}
	int mid=(L+R)>>1;
	if(l<=mid)
		updata(use[p].lc,L,mid,l,r,z);
	if(r>mid)
		updata(use[p].rc,mid+1,R,l,r,z);
}
void dfs(int x,int fa)
{
	for(re i=head[x];i;i=net[i])
	{
		int p=to[i];
		if(p==fa)
			continue;
		dfs(p,x);
		rt[x]=merge(rt[x],rt[p],1,cnt);
	}
	int ans1=0,ans2=0;
	ans1=query(rt[x],1,cnt,v[x])+1;
	ans2=query(rt[x],1,cnt,v[x]-1);
	if(ans1<=ans2)
		return;
	int l=0,r=v[x],pos=v[x];
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(query(rt[x],1,cnt,mid)<ans1)
		{
			pos=mid;
			r=mid-1;
		}
		else
			l=mid+1;
	}
	updata(rt[x],1,cnt,pos,v[x],1);
}
int main()
{
	scanf("%d",&n);
	for(re i=1;i<=n;i++)
	{
		scanf("%d",&v[i]);
		v2[i]=v[i];
	}
	int a;
	for(re i=1;i<n;i++)
	{
		scanf("%d",&a);
		add(a,i+1);
		add(i+1,a);
	}
	sort(v2+1,v2+n+1);
	cnt=unique(v2+1,v2+n+1)-v2-1;
	for(re i=1;i<=n;i++)
		v[i]=lower_bound(v2+1,v2+cnt+1,v[i])-v2;
	dfs(1,0);
	printf("%d\n",query(rt[1],1,cnt,1));
	return 0;
}

 






posted @ 2021-06-04 15:17  WindZR  阅读(267)  评论(0编辑  收藏  举报