[BZOJ3133] [Baltic2013]ballmachine(树上倍增+堆)
[BZOJ3133] [Baltic2013]ballmachine(树上倍增+堆)
题面
有一个装球机器,构造可以看作是一棵树。有下面两种操作:
从根放入一个球,只要下方有空位,球会沿着树滚下。如果同时有多个点可以走,那么会选择编号最小的节点所在路径的方向。比如依次在树根4
放2个球,第一个球会落到1
,第二个会落到3
:
从某个位置拿走一个球,那么它上方的球会落下来。比如依次拿走5, 7, 8
三个球:
分析
我们可以预处理出从根节点放第i个球时,球到达的位置x.因此,对于每个节点x,我们记录放第几个球的时候才能落到x,记为seq[x]。
预处理的时候先dfs一遍,求出每个节点子树中的最小节点编号。然后再dfs一遍,按照子树中的最小节点编号从小到大遍历。实现上直接sort一遍邻接表即可。容易发现,seq[x]就是x在树的后序遍历序列中处在第几个(因为球会尽量往最下方走)
void dfs2(int x,int fa){ for(int i=0;i<(int)E[x].size();i++){//E[x]已经排过序 int y=E[x][i]; if(y!=fa){ dfs2(y,x); } } seq[x]=++tim; hash_seq[seq[x]]=x; }
然后考虑动态插入和删除。
维护一个最小堆,满足堆顶元素seq最小。插入的时候弹出堆顶,把堆顶对应的节点的状态标记为有球。
删除节点x的时候,x到根节点链上的节点会往下落一层。因此只需要用树上倍增找到x祖先里深度最浅的有球节点,将它的状态标记为无球,然后插入最小堆.
代码
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<cmath> #include<vector> #include<algorithm> #define maxn 100000 #define maxlogn 21 using namespace std; int n,m; int root; vector<int>E[maxn+5]; int log2n; int min_id[maxn+5];//子树中节点的最小编号 int anc[maxn+5][maxlogn+5]; int deep[maxn+5]; bool cmp(int x,int y){ return min_id[x]<min_id[y]; } void dfs1(int x,int fa){ min_id[x]=x; deep[x]=deep[fa]+1; anc[x][0]=fa; for(int i=1;i<=log2n;i++) anc[x][i]=anc[anc[x][i-1]][i-1]; for(int i=0;i<(int)E[x].size();i++){ int y=E[x][i]; if(y!=fa){ dfs1(y,x); min_id[x]=min(min_id[x],min_id[y]); } } } int tim; int seq[maxn+5]; //掉球顺序 int hash_seq[maxn+5];//seq=i的节点编号 int is_ball[maxn+5];//是否有球 priority_queue<int,vector<int>,greater<int> >q;//按落球顺序从小到大,存储没有球的节点 void dfs2(int x,int fa){ for(int i=0;i<(int)E[x].size();i++){ int y=E[x][i]; if(y!=fa){ dfs2(y,x); } } seq[x]=++tim; hash_seq[seq[x]]=x; } int insert(int num){ int ans; for(int i=1;i<=num;i++){ int x=hash_seq[q.top()]; q.pop(); is_ball[x]=1; if(i==num){ ans=x; break; } } return ans; } int del(int x){ int orig_x=x; for(int i=log2n;i>=0;i--){ if(is_ball[anc[x][i]]) x=anc[x][i]; } is_ball[x]=0; q.push(seq[x]); return deep[orig_x]-deep[x]; } int main(){ int f; int op,num; scanf("%d %d",&n,&m); log2n=log2(n)+1; for(int i=1;i<=n;i++){ scanf("%d",&f); if(f==0) root=i; else E[f].push_back(i); } dfs1(root,0); for(int i=1;i<=n;i++) sort(E[i].begin(),E[i].end(),cmp); dfs2(root,0); for(int i=1;i<=n;i++) q.push(seq[i]); for(int i=1;i<=m;i++){ scanf("%d %d",&op,&num); if(op==1) printf("%d\n",insert(num)); else printf("%d\n",del(num)); } }
版权声明:因为我是蒟蒻,所以请大佬和神犇们不要转载(有坑)的文章,并指出问题,谢谢
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,携手博客园推出1Panel与Halo联合会员
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微服务架构学习与思考:微服务拆分的原则
· 记一次 .NET某云HIS系统 CPU爆高分析
· 如果单表数据量大,只能考虑分库分表吗?
· 一文彻底搞懂 MCP:AI 大模型的标准化工具箱
· 电商平台中订单未支付过期如何实现自动关单?
· 精选 4 款免费且实用的数据库管理工具,程序员必备!
· Cursor:一个让程序员“失业”的AI代码搭子
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(6)
· 重生之我是操作系统(七)----内存管理(上)
· .NET 阻止Windows关机以及阻止失败的一些原因