3081 排队

Task
一棵n个节点的树,每个节点容纳1个人。1是入口,有m轮操作,共2种操作。
1. 加入x个人,求最后一个人到达的节点。
规则 ①只要有空位就往下走
② 多个空位,选编号最小的
2. 移走x号节点上的人,x上面的人都落下来,求落下来的个数。

Solution
规则的特征是尽可能先往左边走,再往右边走,最后是根,特征与后序遍历相似。
因此可以用后序遍历确定每个节点的优先级,优先级小的一定比优先级大的先取。
那么当前进入一个人,一定会去空着的节点中优先级最小的节点,即答案是根据优先级大小唯一确定的。
怎么去维护空着的节点,且每次找优先级最大的节点呢?线段树当然是可以的,但是麻烦。最简单的方法是堆。

移走x号节点之后落下来的个数,暴力的方法是一个一个跳上去,找到最后一个1的位置。
而1都是连续的,可以用二分,树上的二分用倍增来实现。

特征信息结合=算法

const int M=1e5+5,S=20;
int n,m,ecnt,tot;
int cmp[M],fa[S][M];//220w
bool vis[M];
vector<int>s[M];
struct node{
    int x;
    bool operator<(const node &t)const{
        return cmp[x]>cmp[t.x];
    }
};
priority_queue<node>Q;
inline void input(){
    int i,j,k,a,b;
    rd(n);rd(m);
    rep(i,1,n-1){
        rd(a);rd(b);
        s[a].pb(b);
        s[b].pb(a);
    }
    rep(i,1,n)sort(s[i].begin(),s[i].end());//编号小的先
}
inline void dfs(int f,int x){//后序遍历 
    fa[0][x]=f;
    int i,t;
    for(i=0;i<s[x].size();i++){
        t=s[x][i];
        if(t==f)continue;
        dfs(x,t);
    }
    cmp[x]=++tot;
}
inline void init(){
    int i,j;
    rep(i,1,n)Q.push((node){i});//加入空的节点
    rep(i,1,S-1)rep(j,1,n)fa[i][j]=fa[i-1][fa[i-1][j]];
}
inline void Add(int x){//按优先级加入 
    --x;
    while(x--){vis[Q.top().x]=1;Q.pop();}
    sc(Q.top().x);
    vis[Q.top().x]=1;
    Q.pop();
}
inline void Delete(int x){//节点x的点被移走,找祖先上第一个1的位置
    int i,sum=0;
    per(i,S-1,0)
        if(vis[fa[i][x]])x=fa[i][x],sum|=(1<<i);
    vis[x]=0;
    Q.push((node){x});//空的点放入堆里 
    sc(sum);
}
inline void solve(){
    int c,k;
    while(m--){
        rd(c);rd(k);
        if(c==1)Add(k);
        else Delete(k);
    }
}
int main(){
//  freopen("0.in","r",stdin);
    input();
    dfs(0,1);
    init();
    solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值