洛谷P3605 [USACO17JAN]Promotion Counting 线段树合并

题目描述

The cows have once again tried to form a startup company, failing to remember from past experience that cows make terrible managers!

The cows, conveniently numbered 1…N (1≤N≤100,000), organize the company as a tree, with cow 1 as the president (the root of the tree). Each cow except the president has a single manager (its “parent” in the tree). Each cow i has a distinct proficiency rating, p(i), which describes how good she is at her job. If cow i is an ancestor (e.g. a manager of a manager of a manager) of cow j, then we say j is a subordinate of i.

Unfortunately, the cows find that it is often the case that a manager has less proficiency than several of her subordinates, in which case the manager should consider promoting some of her subordinates. Your task is to help the cows figure out when this is happening. For each cow i in the company, please count the number of subordinates j where p(j) > p(i).

输入格式:
The first line of input contains N.

The next N lines of input contain the proficiency ratings p(1) … p(N) for the cows. Each is a distinct integer in the range 1…1,000,000,000.

The next N-1 lines describe the manager (parent) for cows 2…N. Recall that cow 1 has no manager, being the president.

输出格式:
Please print N lines of output. The ith line of output should tell the number of subordinates of cow ii with higher proficiency than cow ii.

题意:给定一棵树,求每个点子节点权值大于本身权值的数量。

思路

离散化+动态开点+权值线段树+线段树合并
首先将所有节点p的值离散化,建一棵权值线段树保存p的值(权值线段树中存在区间内值的数量)。
dfs搜到叶子节点后回溯,每次把孩子节点和父亲节点在线段树中合并(合并过程见下文)。
最后进行区间查询大于节点权值的数量。
此处线段树需用动态开点,并注意线段树大小要开到30倍左右。

线段树合并原理

线段树合并:把两个线段树节点合并为一个,保存的信息整合在一起。
此题中线段树中保存的信息是某区间内值的数量,所以整合时把两权值相加即可。
假设要将u,v合并,
若u或v为空,则返回另一节点。
否则建一新节点t,整合u,v信息(此题中为两权值相加),再递归合并u,v的子树。
合并时需注意维护权值信息。

代码

#include<stdio.h>
#include<cstring>
#include<algorithm>
#define re register int
#define fast static int
using namespace std;
typedef long long ll;
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=10*x+ch-'0';
        ch=getchar();
    }
    return x*f;
}
const int Size=100005;
int n,p[Size],np[Size];
/*p:p的值,经离散化后会改变		np:离散化中排序用的数组*/
struct Edge
{
    int v,next;
} w[Size<<1];			//存边 
int cnt,head[Size];		//链式前向星 
inline void AddEdge(int u,int v)
{
    w[++cnt].v=v;
    w[cnt].next=head[u];
    head[u]=cnt;
}
inline int search(int x)		//二分查找np中x的位置(离散化用) 
{
    re l=1,r=n,mid;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(np[mid]==x)
        {
            return mid;
        } else if(np[mid]<x) {
            l=mid+1;
        } else {
            r=mid-1;
        }
    }
    return l-1;
}
int root[Size],tot,ans[Size];	//root[x]表示x在线段树中位置 
								//tot表示当前线段树中节点总个数 
								//ans记录答案 
struct node {
    int l,r,v;		//当前区间和权值 
    int lc,rc;		//左孩子和右孩子 
} tree[Size*30];
inline void Pushup(int rt)
{
    tree[rt].v=tree[tree[rt].lc].v+tree[tree[rt].rc].v;
}
void Insert(int l,int r,int x,int &rt)		//在线段树中插入节点x 
{
    rt=++tot;
    tree[rt].l=l;
    tree[rt].r=r;
    if(l==r)
    {
        tree[rt].v=1;
        return;
    }
    int mid=(l+r)>>1;
    if(x<=mid)	Insert(l,mid,x,tree[rt].lc);
    if(x>mid)	Insert(mid+1,r,x,tree[rt].rc);
    Pushup(rt);
}
int Query(int l,int r,int rt)			//查询权值在区间[l,r]内的p的个数 
{
//	printf("%d %d %d\n",l,r,rt);
    if(!rt)	return 0;
    if(l<=tree[rt].l && tree[rt].r<=r)
    {
        return tree[rt].v;
    }
    int ans=0,mid=(tree[rt].l+tree[rt].r)>>1;
    if(l<=mid)	ans+=Query(l,r,tree[rt].lc); 
    if(r>mid)	ans+=Query(l,r,tree[rt].rc);
    return ans;
}
int merge(int x,int y)			//合并x,y 
{
    if(x==0 || y==0)	return x|y;
    if(tree[x].l==tree[x].r)
    {
        tree[x].v+=tree[y].v;
        return x;
    }
    tree[x].lc=merge(tree[x].lc,tree[y].lc);
    tree[x].rc=merge(tree[x].rc,tree[y].rc);
    Pushup(x);
    return x;
}
void dfs(int x)					//dfs 
{
    for(int i=head[x]; i; i=w[i].next)
    {
    	dfs(w[i].v);
    	root[x]=merge(root[x],root[w[i].v]);
    }
    ans[x]=Query(p[x]+1,n+1,root[x]);
}
void init()
{
    n=read();
    for(re i=1; i<=n; i++)
    {
        p[i]=read();
    }
    for(re i=2; i<=n; i++)
    {
    	int x=read();
        AddEdge(x,i);
    }
    /*离散化*/
    for(re i=1; i<=n; i++)
    {
        np[i]=p[i];
    }
    n=unique(np+1,np+1+n)-(np+1);
    sort(np+1,np+1+n);
    for(re i=1; i<=n; i++)
    {
        p[i]=search(p[i]);
        Insert(1,n+1,p[i],root[i]);
    }
}
int main()
{
    init();
    dfs(1);
    for(re i=1; i<=n; i++)
    {
        printf("%d\n",ans[i]);
    }
    return 0;
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值