2020-2021 ACM-ICPC Brazil Subregional Programming Contest——E. Party Company

题目大意:

一个公司有n个员工,他们之间的上下级关系可以用一棵树来表示,每个点有一个点权val[i]表示其年龄,保证上司的年龄大于等于下属的年龄。现在要举办m次派对,每次派对有一个主办人u和一个闭区间[L,R],要求派对的参与者年龄必须要属于这个区间(保证主办者的年龄符合此要求),且如果一个员工参与了派对,那么他的符合条件的上司或下属也会参加这次派对,求m次派对之后每个人参加的派对数。

 

这是最近的训练题,当时一开始没注意到保证上司年龄一定比下属大,想了很久。

 

解析:

我们每次在树上查找深度最小的且权值小于R的人(一定存在,因为父亲的权值大于儿子),假设为rt,这样我们本次参加派对人的人就是以rt为根节点的子树中所有权值大于等于L的人。

这道题如果在线操作的话一定会超时,所以我们考虑离线处理,利用上面的性质,我们可以每次都找到本次派对对应的rt,给他打上一个标记L,表示这个点作为了某一次派对的“根节点”且这次派对的权值要求右边界为L,至于为什么这么做,后面会解释。在查找rt的过程中我们可以预处理ST表,这样就可以在mlogn的时间内查找出所有的rt并打上标记。

下面就是整道题的重点了,我们对整棵树做一遍dfs,同时利用一个权值树状数组t,维护权值为1-1e5(即年龄的取值范围)中每个年龄的出现次数,这样getsum(x)就表示所有小于等于L的权值的出现次数(就是我们打的标记)。举个例子,当我们dfs到某个点的时候,我们将所有在这个点打的标记对应的权值的出现次数在树状数组中+1,那么这个时候,在当前这个节点之后遍历到的点,不可能会对当前节点的答案产生影响了,因为后面遍历到的节点上的标记一定表示的是以其为rt的派对,是不会包含我们当前节点的。因此,我们此时所有小于等于当前节点的权值的权值出现次数之和就是我们当前节点参加过的派对次数因为只有以当前节点或者其上方的节点举办的派对当前节点才可能参加,而这个答案就是getsum(当前节点权值),注意回溯的时候需要还原现场(当前节点所有标记对应的权值出现次数-1),否则在回到某个节点往另一颗子树dfs的时候没有消除前一棵子树对答案的影响。

 

AC代码:

#include<iostream>
#include<string.h>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long ll;

int n,m;
vector<int> tag[100100],a[100100];
int ans[100100],t[100100],fa[100100][21],val[100100];//t是一个权值树状数组;fa为ST表 

int lowbit(int x){
    return x&-x;
}

void add(int x,int v){//单点更新 
    for (int i=x;i<100100;i+=lowbit(i)) t[i]+=v;//因为是权值树状数组所以一定要是循环到年龄的最大值 
}

int getsum(int x){//前缀和 
    int ret=0;
    for (int i=x;i;i-=lowbit(i)) ret+=t[i];
    return ret;
}

void ini(){//初始化ST表 
    for (int i=1;i<=20;i++){
        for (int j=1;j<=n;j++) fa[j][i]=fa[fa[j][i-1]][i-1];
    }
}

int get(int x,int r){//倍增找到以x为主办者的派对的参加者的深度最低的节点 
    for (int i=20;i>=0;i--){
        if (val[fa[x][i]]<=r) x=fa[x][i];
    }
    return x;
}

void dfs(int now,int fa){
    for (int nx:tag[now]) add(nx,1);
    ans[now]=getsum(val[now]);
    for (int nx:a[now]){
        if (nx==fa) continue;
        dfs(nx,now);
    }
    for (int nx:tag[now]) add(nx,-1);//还原现场 
}


int main(){
    scanf("%d %d",&n,&m);
    for (int i=1;i<=n;i++){
        scanf("%d %d",&val[i],&fa[i][0]);
        if (i!=1) a[fa[i][0]].push_back(i);
    }
    ini();
    while (m--){
        int u,l,r;
        scanf("%d %d %d",&u,&l,&r);
        int rt=get(u,r);
        tag[rt].push_back(l);
    }
    dfs(1,1);
    for (int i=1;i<=n;i++) cout<<ans[i]<<" ";cout<<endl;
    return 0;
}

 

posted @ 2020-12-07 16:15  White_Li  阅读(342)  评论(0)    收藏  举报