题目描述
Solution
虽然这道题一次A掉。但是这道题的做题思路非常有帮助
首先考虑这个题的第一个问:
首先第一点:知道当前这颗树中那些房间有没有人的情况下,每个点进入到最终的目标是确定的,进而可以发现,每一个点对应了一个优先级,每个点必须进入当先优先级最高而且房间为空的点。
这里会有两个问题:
怎么寻找每个点的优先级:
答案很明显DFS:每次对于每个点的子节点,先DFS子节点中最小的点,最后递归返回的点的顺序即使优先级从大到小的顺序。
那么如何寻找子节点中最小的点呢:把每个节点的子节点预处理排序(或者直接用优先队列保存)即可。
怎么查找当前优先级最高的点:堆(优先队列)
首先把所有的节点入堆,堆中保存的是节点的优先级,这样堆顶就理所当然保存的是当前优先级最高的点。
第一问:
初始状态堆中保存了所有的节点,说明所有的节点都可以进入。
若进入n个人:只需要依次弹出n个堆顶即可。
第二问:
对于使一个节点n变成空:
如果该节点的人被移走,那么所有这个点以上的点都会依次向下走:最终的结果是,n点仍然有人,即不在堆中。因为上面的人会走下来占领它。然后距离n点最远的有人的房间m由于会向下走一个点。m点变成空,即m进入优先队列。
这里用倍增来寻找m点,输出跳跃的距离ans+=(1<<i) 即可
问题得到解决
最后请注意为什么会想到堆。
利用堆的目的是寻找当前优先级最高的点:也许最开始会思考,优先级最高的点就是当前所有的点里面值最大的点,为什么用堆?
原因:第二问中m点由于变成空节点,会进入优先队列。那么由于不知道b在当前集合中排第几位,所以用优先队列快速longn时间加入队列 否则O(n)枚举点很显然是超时的。
总结:堆并不是特别复杂的数据结构,其结构特点是让堆中的节点保持有序,这样可以快速插入数。
贴一下代码~
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<vector>
#define M (100005)
using namespace std;
priority_queue <int> q;
priority_queue <int> tree[M];
int n,t,a,b,ord[M],tot=0,vis[M],F[20][M],num[M],exist[M],ans;
void Find_Order(int s)
{
while(!tree[s].empty())
{
int i=-tree[s].top();tree[s].pop();
if(!vis[i]){
F[0][i]=s;
for (int k=1;k<=19;k++)
F[k][i]=F[k-1][F[k-1][i]];
vis[i]=1;
Find_Order(i);
}
}
ord[s]=++tot;
num[tot]=s;
}
int main()
{
//freopen("queue.in","r",stdin);
scanf("%d%d",&n,&t);
for (int i=1;i<=n-1;i++){
scanf("%d%d",&a,&b);
tree[a].push(-b);tree[b].push(-a);
};
vis[1]=1;Find_Order(1);
for (int i=1;i<=n;i++){
q.push(-i);
exist[num[i]]=1;
}
while(t--){
scanf("%d%d",&a,&b);
if(a==1){
while(b--) {
int topp=-q.top();
ans=num[topp];
q.pop();
exist[num[topp]]=0;
}
printf("%d\n",ans);
}
if(a==2){
ans=0;
for (int i=19;i>=0;i--){
if(!exist[F[i][b]]&&F[i][b]!=0){
ans+=(1<<i);
b=F[i][b];
}
}
q.push(-ord[b]); exist[num[ord[b]]]=1;
printf("%d\n",ans);
}
}
}