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;
}