【题解】晋升者计数 Promotion Counting [USACO 17 JAN] [P3605]


奶牛们又一次试图创建一家创业公司,还是没有从过去的经验中吸取教训。!牛是可怕的管理者!


【题目描述】

奶牛从 \(1\) ~ \(N(1≤N≤1e5)\) 进行了编号,把公司组织成一棵树,\(1\)号奶牛作为总裁(树的根节点)。除总裁以外的每头奶牛都有且仅有唯一的一个的上司(即它在树上的父结点)。每一头牛\(i\)都有一个不同的能力指数 \(p(i)\),描述了她对其工作的擅长程度。如果奶牛 \(i\) 是奶牛 \(j\) 的父亲或祖先节点,那么奶牛 \(j\) 即为 \(i\) 的下属。

不幸地是,奶牛们发现经常有一个上司比她的下属能力低的情况,在这种情况下,上司应当考虑晋升她的一些下属。你的任务是对于公司的中的每一头奶牛 \(i\),计算其满足 \(p(j)>p(i)\) 的下属 \(j\) 的数量。

【输入】

输入的第一行包括一个整数 \(N\)。

接下来的 \(N\) 行表示每头奶牛的能力指数 \(p(1),p(2)...p(N)\),接下来的 \(N−1\) 行描述每头奶牛 \((2\) ~ \(N)\) 的上司(父节点)的编号。

【输出】

输出包括 \(N\) 行,输出的第 \(i\) 行表示奶牛 \(i\) 的下属中能力比奶牛 \(i\) 高的数量。

【样例】

样例输入:
5
804289384
846930887
681692778
714636916
957747794
1
1
2
3 样例输出:
2
0
1
0
0

【数据规模】

\(100\%\) \(1 \leqslant N \leqslant 1e5,1 \leqslant p(i) \leqslant 1e9\)

【分析】

不会线段树的先到隔壁去逛逛:线段树详解(全)

问题实质:求树上逆序对

解法一: 树状数组 / 线段树 + dfs

对能力值开一个权值线段树\((\)范围较大 \(1e9\),加一个离散化\()\)。

对于每个节点 \(i\),在线段树中查询数值大于 \(a[i]\) 的数的个数,再对于点 \(i\) 所有儿子节点的权值都进行一次【单点修改】操作,将它们都加入进去,再一次进行同样的查询,用其减去前面查询的结果就得到变化量 ∆,即刚刚加入的这个儿子所统领的树\((\)包括它自己\()\)中能力大于 \(p(i)\) 的牛的数量,即 \(ans[i]\)。树状数组可用同样的思路。

【Code】

(线段树)

#include<algorithm>
#include<cstdio>
#define pl p<<1//左段儿子的编号
#define pr p<<1|1//右段儿子的编号
#define Re register int
#define F(a,b) for(i=a;i<=b;++i)
const int N=1e5+3;
int x,i,n,t,Q[N],ip[N],ans[N],nex[N],head[N],tree[N<<2];
struct A{int x,i;bool operator<(A b)const{return x<b.x;};}a[N];
//【重载"<"运算符】自己动手,丰衣足食...
inline void add(Re x,Re y){Q[++t]=y,nex[t]=head[x],head[x]=t;}//数组维护一个链表
inline void in(Re &x){//【快读】自己动手,丰衣足食...
x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
inline void change(Re p,Re x,Re L,Re R){
//【单点修改】每个人仅有一个能力值,所以是单点修改
if(L==R){++tree[p];return;}//单点修改的边界:L==R
Re mid=L+R>>1;
if(x<=mid)change(pl,x,L,mid);//注意:单点修改中,进入递归函数的前提:x<=mid
else change(pr,x,mid+1,R);//x>mid
tree[p]=tree[pl]+tree[pr];//递归完子树后更新
}
inline int ask(Re p,Re l,Re r,Re L,Re R){//【区间查询】
if(l<=L&&R<=r)return tree[p];//区间查询的边界: l<=L&&R<=r
Re ans=0,mid=L+R>>1;
if(l<=mid)ans+=ask(pl,l,r,L,mid);//注意区间查询中,进入递归函数的前提:l<=mid
if(r>mid)ans+=ask(pr,l,r,mid+1,R);//r>mid
return ans;
}
inline void dfs(Re x){//搜索遍历树
ans[x]-=ask(1,ip[x]+1,n,1,n);//这里计算出的是其他节点的数据,要减去
for(Re i=head[x];i;i=nex[i])dfs(Q[i]);//先让儿子更新线段树【单点修改】
ans[x]+=ask(1,ip[x]+1,n,1,n);
//查询大于当前这个点x的等级的一共有多少个【区间查询】,再减去原先的
change(1,ip[x],1,n);//让当前这个点x的等级进行【单点修改】
}
int main(){
in(n);
F(1,n)in(a[i].x),a[i].i=i;
std::sort(a+1,a+n+1);//【离散化】依据每个人的能力p(i)排出他们的等级
F(1,n)ip[a[i].i]=i;//用ip[]记录下每个点的等级
F(2,n)in(x),add(x,i);//链表建树,实现从根节点到儿子叶节点的遍历
dfs(1);//从没有上司的根节点1开始遍历
F(1,n)printf("%d\n",ans[i]);
}

(树状数组)

#include<algorithm>
#include<cstdio>
#define Re register int
#define F(a,b) for(i=a;i<=b;++i)
const int N=1e5+3;
int x,i,n,t,C[N],Q[N],ip[N],ans[N],nex[N],head[N];
struct A{int x,i;bool operator<(A b)const{return x<b.x;};}a[N];
inline void add(Re x,Re y){Q[++t]=y,nex[t]=head[x],head[x]=t;}
inline void in(Re &x){
x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
inline void addx(Re x){while(x<=n)++C[x],x+=x&(-x);}
inline int ask(Re x){int ans=0;while(x)ans+=C[x],x-=x&(-x);return ans;}
inline void dfs(Re x){
ans[x]-=ask(n)-ask(ip[x]);
for(Re i=head[x];i;i=nex[i])dfs(Q[i]);
ans[x]+=ask(n)-ask(ip[x]);
addx(ip[x]);
}
int main(){
in(n);
F(1,n)in(a[i].x),a[i].i=i;
std::sort(a+1,a+n+1);
F(1,n)ip[a[i].i]=i;
F(2,n)in(x),add(x,i);
dfs(1);
F(1,n)printf("%d\n",ans[i]);
}

解法二:线段树合并

对于 \(N\) 个点每个点都开一个权值线段树。

对公司上司下属关系形成的树进行\(dfs\)遍历,对于每个节点 \(i\) ,将它所有儿子的权值线段树合并到 \(tree[pt[i]](\) 用 \(pt[i]\) 表示节点 \(i\) 所对应的权值线段树的根节点编号\()\),然后在 \(tree[pt[i]]\) 中查询数值大于 \(a[i]\) 的数的个数即为 \(ans[i]\)。

【Code】

#include<algorithm>
#include<cstdio>
#define pl tr[p].lp//左段儿子的编号
#define pr tr[p].rp//右段儿子的编号
#define Re register int
#define F(a,b) for(i=a;i<=b;++i)
const int N=1e5+3;
int x,i,n,t,cnt,Q[N],pt[N],ip[N],ans[N],nex[N],head[N];
struct QAQ{int g,lp,rp,L,R;}tr[N*18];//这里的空间消耗要注意,开太小死活过不了
struct A{int x,i;bool operator<(A b)const{return x<b.x;};}a[N];
//【重载"<"运算符】自己动手,丰衣足食...
inline void add(Re x,Re y){Q[++t]=y,nex[t]=head[x],head[x]=t;}//数组维护一个链表
inline void in(Re &x){//【快读】自己动手,丰衣足食...
x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
inline void change(Re p,Re x){//【单点修改】(虽然在这道题中没什么用)
Re L=tr[p].L,R=tr[p].R;//把编号为p的这个区间所覆盖的范围L,R提出来
if(L==R){++tr[p].g;return;}//单修的递归边界:L==R
Re mid=L+R>>1;
if(x<=mid)change(pl,x);//注意:单修中进入递归函数的前提:x<=mid
else change(pr,x);//x>mid
tr[p].g=tr[pl].g+tr[pr].g;//递归完子树后更新
}
inline void creat(Re &p,Re x,Re L,Re R){//建立线段树
tr[p=++cnt].L=L,tr[p].R=R;//动态开点,并记录新编号子树所覆盖的区间范围L,R
if(L==R){tr[p].g=1;return;}//建立线段树时是一个点一个点的建,所以边界类似于单修
Re mid=L+R>>1;
if(x<=mid)creat(pl,x,L,mid);//递归进入前提也类似于单修
else creat(pr,x,mid+1,R);
tr[p].g=tr[pl].g+tr[pr].g;//递归完子树后更新
}
inline int merge(Re p,Re q){//【线段树合并】
if(!p)return q;if(!q)return p;
//当需要合并的点的其中一个编号为0时 (即为空),返回另一个编号
tr[p].g+=tr[q].g,p;//把q合并到p上面去
pl=merge(pl,tr[q].lp);//分别合并左子树,右子树
pr=merge(pr,tr[q].rp);
return p;
}
inline int ask(Re p,Re l,Re r){//【区间查询】
if(!p)return 0;
Re L=tr[p].L,R=tr[p].R;
if(l<=L&&R<=r)return tr[p].g;//区间查询的边界: l<=L&&R<=r
Re ans=0,mid=L+R>>1;
if(l<=mid)ans+=ask(pl,l,r);//注意区间查询中,进入递归函数的前提:l<=mid
if(r>mid)ans+=ask(pr,l,r);//r>mid
return ans;
}
inline void dfs(Re x){//搜索遍历树
for(Re i=head[x];i;i=nex[i]){
dfs(Q[i]);//先完善儿子的线段树
merge(pt[x],pt[Q[i]]);//把儿子合并进来
}
ans[x]=ask(pt[x],ip[x]+1,n);
//查询大于当前这个点x的等级的一共有多少个【区间查询】,再减去原先的
}
int main(){
in(n);
F(1,n)in(a[i].x),a[i].i=i;
std::sort(a+1,a+n+1);//【离散化】依据每个人的能力p(i)排出他们的等级
F(1,n)ip[a[i].i]=i,creat(pt[a[i].i],i,1,n);
//用ip[]记录下每个点的等级,并建立线段树
//【注意】:进入建树时的编号是原本输入时的编号即a[i].i
F(2,n)in(x),add(x,i);//链表建搜索树,实现从根节点到儿子叶节点的遍历
dfs(1);//从没有上司的根节点1开始遍历
F(1,n)printf("%d\n",ans[i]);
}

【题解】晋升者计数 Promotion Counting [USACO 17 JAN] [P3605]的更多相关文章

  1. usaco 17.Jan 铜组T3

    上午在打usaco月赛的铜组题,T1T2是用来秒杀的,然而T3卡了一上午,下面给出题面: 题意大概就是输入一个N*N的矩阵,矩阵中元素只有0与1两种状态,每次操作以左上角的点为矩阵中某一矩阵的左上方顶 ...

  2. 线段树合并 || 树状数组 || 离散化 || BZOJ 4756: [Usaco2017 Jan]Promotion Counting || Luogu P3605 [USACO17JAN]Promotion Counting晋升者计数

    题面:P3605 [USACO17JAN]Promotion Counting晋升者计数 题解:这是一道万能题,树状数组 || 主席树 || 线段树合并 || 莫队套分块 || 线段树 都可以写..记 ...

  3. Luogu3605 [USACO17JAN]Promotion Counting晋升者计数

    Luogu3605 [USACO17JAN]Promotion Counting晋升者计数 给一棵 \(n\) 个点的树,点 \(i\) 有一个权值 \(a_i\) .对于每个 \(i\) ,求 \( ...

  4. 树状数组 P3605 [USACO17JAN]Promotion Counting晋升者计数

    P3605 [USACO17JAN]Promotion Counting晋升者计数 题目描述 奶牛们又一次试图创建一家创业公司,还是没有从过去的经验中吸取教训--牛是可怕的管理者! 为了方便,把奶牛从 ...

  5. 洛谷P3605 [USACO17JAN] Promotion Counting 晋升者计数 [线段树合并]

    题目传送门 Promotion Counting 题目描述 The cows have once again tried to form a startup company, failing to r ...

  6. [USACO17JAN] 晋升者计数 dfs序+树状数组

    [USACO17JAN] 晋升者计数 dfs序+树状数组 题面 洛谷P3605 题意:一棵有点权的树,找出树中所有\((u,v)\)的对数,其中\(u,v\)满足\(val(u)\le val(v)\ ...

  7. 洛谷 P1596 [USACO10OCT]湖计数Lake Counting

    题目链接 https://www.luogu.org/problemnew/show/P1596 题目描述 Due to recent rains, water has pooled in vario ...

  8. 《算法导论》——计数排序Counting Sort

    今天贴出的算法是计数排序Counting Sort.在经过一番挣扎之前,我很纠结,今天这个算法在一些scenarios,并不是最优的算法.最坏情况和最好情况下,时间复杂度差距很大. 代码Countin ...

  9. [BZOJ4756][Usaco2017 Jan]Promotion Counting 树状数组

    4756: [Usaco2017 Jan]Promotion Counting Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 305  Solved: ...

随机推荐

  1. python爬虫24 | 搞事情了,用 Appium 爬取你的微信朋友圈。

    昨天小帅b看到一些事情不顺眼 有人偷换概念 忍不住就写了一篇反讽 996 的 看不下去了,我支持996,年轻人就该996! 没想到有些人看不懂 这就算了 还来骂我 早些时候关注我的小伙伴应该知道我第一 ...

  2. Spring 4 整合RMI技术及发布多个服务(xjl456852原创)

    rmi需要建立两个项目,一个是服务端的项目,一个是客户端的项目.服务端项目启动后,再启动客户端项目去调用服务端的方法. 我们建立两个maven项目: pom.xml配置: <?xml versi ...

  3. 【Codeforces 567D】One-Dimensional Battle Ships

    [链接] 我是链接,点我呀:) [题意] 长度为n的一个序列,其中有一些部分可能是空的,一些部分是长度为a的物品的一部分 (总共有k个长度为a的物品,一个放在位置i长度为a的物品会占据i,i+1,.. ...

  4. HDU 1274 递归拼接字符串

    题目大意: 根据所给的数字,表示其相连的字符的输出个数,或是下一个括号中的所有字符的输出个数 每一个相互对应的 '(' 和 ')' 中的所有字母均作为一组数据处理 在每一次dfs过程中都处理好这样一个 ...

  5. Uva10562

    Professor Homer has been reported missing. We suspect that his recent research works might have had ...

  6. tyvj1117 拯救ice-cream

      背景 天好热……Tina顶着那炎炎的烈日,向Ice-cream home走去……可是……停电了……冰淇淋们躺在Ice-cream home的冰柜里,慢慢地……慢慢地……融化…………你说,她能赶在冰 ...

  7. hdu3303

    分析:一个最暴力的想法是把加入到集合S的数据一个个按顺序保存起来,然后每次查询的时候由后向前计算余数,如果遇到余数为0的,就直接把时间输出,否则就一直比较到最后找余数最小时间最晚的,这样查询的时间复杂 ...

  8. [bzoj1577][Usaco2009 Feb]庙会捷运Fair Shuttle_贪心_线段树

    庙会捷运 Fair Shuttle bzoj-1577 Usaco-2009 Feb 题目大意:有一辆公交车从1走到n.有m群奶牛从$S_i$到$E_i$,第i群奶牛有$W_i$只.车有一个容量c.问 ...

  9. 常见的HTTP状态码(HTTP Status Code)

    HTTP状态码 当使用浏览器访问一个网页时,浏览器会向网页所在服务器发出请求.当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含HTTP状态码的信息头(server header)用以响应浏览 ...

  10. Linux声卡驱动移植和測试

    一.分析驱动程序,依据开发板改动代码 代码太长,就不贴了,几个注意点: 1. 查看开发板原理图和S3C2410的datasheet,UDA1341的L3MODE.L3DATA.L3CLOCK分别与S3 ...