【ybt金牌导航4-1-4】【luogu P3261】城池攻占

城池攻占

题目链接:ybt金牌导航4-1-4 / luogu P3261

题目大意

给你一个有根树,然后点有权值,和改变系数。
改变系数可能是乘是个正数,也可能是加一个数。
然后又一些人,它们初始的属性值和位置已经确定,它们要往上跳,属性值如果小于当前点权就停下,否则就根据当前点改变系数改变,然后往上跳,直到停止或跳到根节点的上面。
问你每个人能跳多少次,有多少个人会在这个点停下。

思路

容易想到由于你修改属性值要么叫一个数,要么乘一个正数,那无论怎么修改,人们属性值的大小关系时不变的。

那我们考虑搞一个堆,然后每次看要不要杀死就一直拿出堆顶,判断它的权值,要杀死就杀死,并记录相关的答案。
然后你发现每个点都代表一个堆,然后你可以看到某个点的堆可以有它的儿子的堆和从它出发的点合并得到,自然想到左偏树。

然后接着问题就变成了如何维护左偏树里面数的值。
你考虑想搞线段树一样,用一个 lazy 懒标记来搞。
当你要合并的时候,你合并要用到两个左偏树的儿子的值,所以你这个时候把两个左偏树的标记都下传。
当你要删左偏树最上面的点的时候,它下面的信息还没有更新,直接删就丢掉了,所以要下传了之后再删。

至于下传,跟线段树其实是一样的。
为什么可以用懒标记呢,因为你每次需要的只是左偏树的根节点,那懒标记就是只求出根节点,你要下面的点的时候再往下传。

主要是具体实现比较烦,看看代码就行了。
(不开 long long 见祖宗)

代码

#include<queue> #include<cstdio> #include<algorithm> #define ll long long using namespace std; struct node { int to, nxt; }e[600001]; struct left_tree { int l, r, dy, dis, st; ll x, add, times; }a[300001]; int n, m, fa[300001], fir[300001]; ll h[300001], v[300001], s[300001]; int op[300001], le[300001], KK, tot; int killnum[300001], deg[300001], runroad[300001]; int root[300001]; void add(int x, int y) { e[++KK] = (node){y, le[x]}; le[x] = KK; e[++KK] = (node){x, le[y]}; le[y] = KK; } void down(int x) {//向下传懒标记 if (a[x].l) { a[a[x].l].times *= a[x].times; a[a[x].l].add *= a[x].times; a[a[x].l].add += a[x].add; a[a[x].l].x = a[a[x].l].x * a[x].times + a[x].add; } if (a[x].r) { a[a[x].r].times *= a[x].times; a[a[x].r].add *= a[x].times; a[a[x].r].add += a[x].add; a[a[x].r].x = a[a[x].r].x * a[x].times + a[x].add; } a[x].times = 1; a[x].add = 0; } int merge(int x, int y) { if (!x) return y; if (!y) return x; down(x);//合并要用到点儿子的信息,所以要向下传递 down(y); if (a[x].x > a[y].x) swap(x, y); a[x].r = merge(a[x].r, y); if (a[a[x].l].dis < a[a[x].r].dis) swap(a[x].l, a[x].r); a[x].dis = a[a[x].r].dis + 1; return x; } int delete_top(int now) { return merge(a[now].l, a[now].r); } void dfs(int now, int father) { deg[now] = deg[father] + 1; for (int i = le[now]; i; i = e[i].nxt) if (e[i].to != father) { dfs(e[i].to, now); root[now] = merge(root[now], root[e[i].to]);//先全部合并在一起 } while (root[now] && a[root[now]].x < h[now]) {//再把要杀死的杀死 down(root[now]); killnum[now]++; runroad[a[root[now]].dy] = deg[a[root[now]].st] - deg[now];//计算深度(你开始跳的深度-你停下的深度) root[now] = delete_top(root[now]); } if (op[now]) {//修改属性值(只用修改根节点和懒标记) a[root[now]].times *= v[now]; a[root[now]].add *= v[now]; a[root[now]].x *= v[now]; } else { a[root[now]].add += v[now]; a[root[now]].x += v[now]; } } int main() { scanf("%d %d", &n, &m); for (int i = 1; i <= n; i++) scanf("%lld", &h[i]); for (int i = 2; i <= n; i++) { scanf("%d %d %lld", &fa[i], &op[i], &v[i]); add(i, fa[i]); } for (int i = 1; i <= m; i++) { scanf("%lld %d", &s[i], &fir[i]); a[i].st = fir[i]; a[i].x = s[i]; a[i].times = 1; a[i].dy = i; root[fir[i]] = merge(root[fir[i]], i);//把在同一个点出发的人都弄进一个树中 } dfs(1, 0); while (root[1]) {//最后还有没有被杀死的人 down(root[1]); runroad[a[root[1]].dy] = deg[a[root[1]].st];//那跳的次数就是它出发时的深度 root[1] = delete_top(root[1]); } for (int i = 1; i <= n; i++) printf("%d\n", killnum[i]); for (int i = 1; i <= m; i++) printf("%d\n", runroad[i]); return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/YBT_JPDH_4-1-4.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(37)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示