LOJ #2135. 「ZJOI2015」幻想乡战略游戏(点分树)

题意

给你一颗 n 个点的树,每个点的度数不超过 20 ,有 q 次修改点权的操作。

需要动态维护带权重心,也就是找到一个点 v 使得 vwv×dist(u,v) 最小。

数据范围

n105,q105,v,wv0

题解

Update on 2019.3.29: 似乎可以二叉化就可以不用保证度数了。。

首先了解一个重心的重要性质:

对于一条边 xy 如果 x 侧子树和 > y 侧子树和,那么重心一定在 x 侧。

值得一提的是这个结论对于 distk(x,i) 都是成立的,一般情况下都需要快速求出树的带权重心。

利用这个结论可以快速求出 带权重心

暴力求的话,在随机数据下表现非常优秀,但是我们明显可以用一些数据结构来优化这个过程,此时不难想到 点分树

因为对于上面那个找 带权重心 的过程,我们可以考虑分治解决。

  1. 首先先到整棵树的重心,当做分治操作的起点。
  2. 每次考虑枚举它所有点分树上的儿子,然后向着 sumchildy2>Sum 的方向去走,也就是向着子树权值和大于总权值一半的方向。
  3. 然后我们接下来就可以到这个子树所对应的分治中心,继续进行分治操作。
  4. 直到这个点在点分树上不存在一个儿子满足 sumchildy2>Sum 的要求,此时这个点就是重心。

这样我们最多重复 logn 次操作就停下来,接下来我们就是需要动态求一个子树的 sumy

这个显然可以用树剖线段树等数据结构进行维护,但我们有了点分树显然这样是多余的。

我们考虑对于每个点分树上每个点,维护它子树所有点的 wi 的和,记为 sumi=vchild(i)wv

我们每次从 xy 向下分治的时候,令 vxy原树 路径上除 x 外第一个点。

我们考虑把 vy 在点分树路径上的所有 sum 加上 sumxsumy 也就是 x 部分的点权。

至于这样为什么是对的。简单说明下,这样就会对接下来所有需要算上 x 部分贡献的分治重心进行贡献。这样我们就保证了所有要算上的点都是算上的正确的答案。

注意做完后需要减回来。


然后我们找到了重心,考虑计算答案。

有两种方法。

  1. 第一种是类似于 「HNOI2015」开店 其中一个做法,利用满足差分的性质。

    也就是 i=1nwidist(x,i)=i=1nwi(di+dx)2i=1nwidlca(i,x) 的特性。(此处 dii 的深度)

    对于每个点将其到根路径链上的点加上 wi ,然后询问 x 到根的路径点权和 res

    i=1nwidi+(i=1nwi)dx 减去 2res 就行了。然后用树剖后,利用线段树就可以动态维护了。

  2. 但显然此处,我们还是有着点分树这个强大的树上结构,可以考虑换一种方式来维护。

    我们在之前维护 sumu 的基础上,多维护两个东西。

    • totu :点分树上 u 的子树里所有点的到 u 的带权距离和 vchild(u)wv×dist(v,u)
    • totfau :点分树上u 的子树里所有点的到 u 在点分树上的父亲 fa 的带权距离和 vchild(u)wv×dist(v,fa)

    然后询问 pos 节点的时候。我们考虑每次在点分树向上跳,并计算贡献。

    假设当前从 vu ,把 ans 加上 (sumusumv)×dist(pos,u) ,这个意思就是把 v 外面所有的点加上这条边权的答案。

    但是这样显然算少了,因为 v 外面所有点到 u 的距离没有算上,所以还要加上 totutotfav 这部分贡献就行了。

    注意 ans 一开始的时候初值是 totpos

    然后为了代码没有那么毒瘤,对于此处的树上距离,我们可以预处理出每个点到它点分树上的祖先的距离,因为我们只需要用上这些点对的距离。

总结

对于一些动态有关树上距离的问题,我们可以考虑点分树之类的强大数据结构。

然后带权重心都可以满足之前那个性质,可以用点分树上去找。

代码

强烈建议看看我的代码!! 写的真的优秀!!

#include <bits/stdc++.h> #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << x << endl #define DEBUG(...) fprintf(stderr, __VA_ARGS__) using namespace std; inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;} inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;} inline int read() { int x = 0, fh = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1; for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48); return x * fh; } void File() { #ifdef zjp_shadow freopen ("2135.in", "r", stdin); freopen ("2135.out", "w", stdout); #endif } const int N = 1e5 + 1e3, M = N << 1; typedef long long ll; int Head[N], Next[M], to[M], val[M], e; inline void add_edge(int u, int v, int w) { to[++ e] = v; Next[e] = Head[u]; Head[u] = e; val[e] = w; } inline void Add(int u, int v, int w) { add_edge(u, v, w); add_edge(v, u, w); } #define Travel(i, u, v) for(register int i = Head[u], v = to[i]; i; v = to[i = Next[i]]) bitset<N> vis; int sz[N], maxsz[N], rt, nodesum; void Get_Root(int u, int fa = 0) { sz[u] = maxsz[u] = 1; Travel(i, u, v) if (v != fa && !vis[v]) Get_Root(v, u), sz[u] += sz[v], chkmax(maxsz[u], sz[v]); chkmax(maxsz[u], nodesum - sz[u]); if (maxsz[u] < maxsz[rt]) rt = u; } ll dis[N][20]; int from[N], cur[N]; void Get_Dis(int u, ll dep, int fa, int anc) { if (fa) from[u] = anc, dis[u][cur[u] ++] = dep; Travel(i, u, v) if (!vis[v] && v != fa) Get_Dis(v, dep + val[i], u, anc); } typedef pair<int, ll> PII; #define fir first #define sec second #define mp make_pair vector<PII> Sub[N]; void Dfs_Div(int u = 1) { vis[u] = true; Get_Dis(u, 0, 0, u); Travel(i, u, v) if (!vis[v]) rt = 0, nodesum = sz[v], Get_Root(v), Sub[u].push_back(mp(rt, v)), from[rt] = u, Dfs_Div(rt); } ll sum[N], tot[N], tot_fa[N]; inline void Update(int pos, int uv) { tot_fa[pos] += dis[pos][0] * uv; for (register int u = pos, dep = 0; u; u = from[u], ++ dep) { sum[u] += uv; tot[from[u]] += dis[pos][dep] * uv; tot_fa[from[u]] += dis[pos][dep + 1] * uv; } } PII cache[N]; int len = 0; ll Sum; int Find_Root(int u) { for (PII it : Sub[u]) { register int v = it.fir; if (sum[v] * 2 > Sum) { register int pos; ll sumu; for(pos = it.sec, sumu = Sum - sum[v]; pos != from[u]; pos = from[pos]) sum[pos] += sumu, cache[++ len] = mp(pos, sumu); return Find_Root(v); } } return u; } int bas; inline ll Query() { register int pos = Find_Root(bas); For (i, 1, len) sum[cache[i].fir] -= cache[i].sec; len = 0; ll res = tot[pos]; for (register int u = from[pos], Last = pos, dep = 0; u; u = from[Last = u], ++ dep) res += tot[u] - tot_fa[Last] + (sum[u] - sum[Last]) * dis[pos][dep]; return res; } signed main () { File(); int n = read(), q = read(); For (i, 1, n - 1) { int u = read(), v = read(), w = read(); Add(u, v, w); } maxsz[rt = 0] = nodesum = n; Get_Root(1); Dfs_Div(bas = rt); For (i, 1, n) if(cur[i]) reverse(dis[i], dis[i] + cur[i]); while (q --) { int pos = read(), uv = read(); Sum += uv; Update(pos, uv); printf ("%lld\n", Query()); } return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/9432872.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(348)  评论(2编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示