【ybt金牌导航5-4-4】【luogu P4842】城市旅行

城市旅行

题目链接:ybt金牌导航5-4-4 / luogu P4842

题目大意

给你一棵树,要你维护一些操作:
删除某条边(如果两点间不联通就不管)
添加某条边(如果两点间已联通就不管)
给某条路径上的点点权加一个值(如果两点不连通就不管)
询问某条路径上任选两个点,这两个点之间路径的权值和的期望。(如果两点不连通就输出 -1)

思路

看到加边删边找路径,自然想到 LCT。
然后我们考虑如何维护输出的值。

那容易想到,我们可以补考虑选的点,而是考虑一个点,有多少个路径会经过它。
那容易想到对于长度为 sz 的路径,对于第 i 个点,有 i×(szi+1) 个路径经过了它。(左右各选一个)
那它的贡献就是 i×(szi+1)×ai

那总贡献就是:i=1szi×(szi+1)×aiCsz+12
(下面就是 Csz+12 因为选的两个点可以是同一个点)

那我们要维护的就是它了,分母很好搞,直接每次算就行,问题是分子。
那我们考虑 DP,已经求出了左右子树,要怎么搞到它。
我们设左子树的大小是 b0,然后序列是 b1,b2,...,bb0,右子树大小是 c0,序列是 c1,c2,...,cc0

那对于左子树里面的第 i 个点,它在左子树里面的贡献就是 i×(b0i+1)×bi,它在这里的贡献就是 i×(b0+c0+1i+1)×bi。作差,就是 i×bi×(c0+1)
那左子树的贡献就是它原本的贡献加上 (c0+1)×i=1b0i×bi
那我们发现右边的部分(i=1b0i×bi)我们也可以 DP,我是用 lsum 数组记录,这里就不讲了,不会的自己看代码。

那接着右子树用同样的方法:
原本:i×(c0i+1)×ci
现在:(b0+1+i)×(b0+c0+1(b0+1+i)+1)×ci
=(b0+1+i)×(c0i+1)×ci
差:(b0+1)×(c0i+1)×ci
那左子树的贡献就是它原本的贡献加上 (b0+1)×i=1b0(c0i+1)×ci
然后右边部分(i=1b0(c0i+1)×ci)继续 DP,我是用 rsum 数组记录。

接着就是新的点,那这个其实容易,就直接暴力算:a×(b0+1)×(c0+1)。(记得加一)

那查询我们就搞定了,接着,就是修改了。(加边删边就是普通 LCT,不用搞)
那我们也是懒标记,那每次要怎么改呢?
首先权值就普通的加,权值和就加上它乘大小。
接着是 lsum,rsum,容易看到你每个数每加 x,值就会多 x+2x+3x+4x+...,那就是 x×(1+sz)×sz/2
那接着就是 ans,即期望的分子,那我们可以列出式子:ans+=i=1szi×(szi+1)×d

然后我们由化简可以得到 ans+=sz(sz+1)(sz+2)6×d
然后就可以搞啦!

化简过程

知道的可以不看。
要搞的东西:i=1szi×(szi+1)=sz(sz+1)(sz+2)6
首先考虑让其中一项固定:
i=1szi×(szi+1)=i=1szi×szi=1szi×(i1)
然后右边部分考虑去括号:i=1szi×szi=1sz(i2i)
分别拿出来:sz×i=1szii=1szi2+i=1szi
然后都可以去掉 sz×(sz+1)×sz2sz(sz+1)(2×sz+1)6+(sz+1)×sz2
合并一下:3(sz+1)(sz+1)sz6sz(sz+1)(2sz+1)6
(3sz+3)(sz+1)sz6(2sz+1)(sz+1)sz6
(sz+2)(sz+1)sz6
然后就好啦!

可能有人(指我自己)会不知道为什么 i=1szi2=sz(sz+1)(2sz+1)6
然后这里也讲讲,这个是用立方差来搞的。
x3(x1)3=x3(x33x2+3x1)=3x23x+1
然后根据这个,我们把 (n3(n1)3)+((n1)3(n2)3)+...+(2313) 每个都转。
那互相消掉,就是 n313=(3n23n+1)+(3(n1)23(n1)+1)+...+(3×223×2+1)
拆开:n31=3n2+3(n1)2+...+3×22(3n+3(n1)+...+3×2+(n1))
然后继续搞:n31=3(n2+(n1)2+...+22)3(n+(n1)+...+2)+(n1)
移项:3(n2+(n1)2+...+22+12)=n31(n1)+3(n+2)(n1)2+3×12
3(n2+(n1)2+...+22+12)=n3n+3+3(n+2)(n1)2
(n2+(n1)2+...+22+12)=2n32n+6+3(n+2)(n1)6
=2n32n+6+3(n2+n2)6=2n3+3n2+n6=n(2n2+3n+1)6=n(n+1)(2n+1)6
然后就有了。

代码

#include<cstdio> #include<algorithm> #define ll long long using namespace std; int n, m, sz[50001], d; int l[50001], r[50001], fa[50001]; ll ans[50001], val[50001], lz[50001]; ll lsum[50001], rsum[50001], sum[50001]; bool lzs[50001]; int op, x, y; //LCT bool nrt(int x) { return l[fa[x]] == x || r[fa[x]] == x; } bool ls(int x) { return l[fa[x]] == x; } void up(int x) {//把推公式推出来的放上去 sz[x] = sz[l[x]] + sz[r[x]] + 1; sum[x] = sum[l[x]] + sum[r[x]] + val[x]; //DP 维护 lsum rsum lsum[x] = lsum[l[x]] + val[x] * (sz[l[x]] + 1) + (lsum[r[x]] + sum[r[x]] * (sz[l[x]] + 1)); rsum[x] = rsum[r[x]] + val[x] * (sz[r[x]] + 1) + (rsum[l[x]] + sum[l[x]] * (sz[r[x]] + 1)); ans[x] = ans[l[x]] + ans[r[x]] + (sz[r[x]] + 1) * lsum[l[x]] + (sz[l[x]] + 1) * rsum[r[x]] + val[x] * (sz[l[x]] + 1) * (sz[r[x]] + 1); } void downa(int x, ll Val) { val[x] += Val; lz[x] += Val; sum[x] += Val * sz[x]; lsum[x] += Val * (1 + sz[x]) * sz[x] / 2; rsum[x] += Val * (sz[x] + 1) * sz[x] / 2; ans[x] += Val * sz[x] * (sz[x] + 1) * (sz[x] + 2) / 6; } void downs(int x) { swap(l[x], r[x]); swap(lsum[x], rsum[x]);//记得这个也要 swap lzs[x] ^= 1; } void down(int x) { if (lzs[x]) { if (l[x]) downs(l[x]); if (r[x]) downs(r[x]); lzs[x] = 0; } if (lz[x]) { if (l[x]) downa(l[x], lz[x]); if (r[x]) downa(r[x], lz[x]); lz[x] = 0; } } void down_line(int x) { if (nrt(x)) down_line(fa[x]); down(x); } void rotate(int x) { int y = fa[x]; int z = fa[y]; int b = (ls(x) ? r[x] : l[x]); if (z && nrt(y)) (ls(y) ? l[z] : r[z]) = x; if (ls(x)) r[x] = y, l[y] = b; else l[x] = y, r[y] = b; fa[x] = z; fa[y] = x; if (b) fa[b] = y; up(y); } void Splay(int x) { down_line(x); while (nrt(x)) { if (nrt(fa[x])) { if (ls(x) == ls(fa[x])) rotate(fa[x]); else rotate(x); } rotate(x); } up(x); } void access(int x) { int lst = 0; for (; x; x = fa[x]) { Splay(x); r[x] = lst; up(x); lst = x; } } void make_root(int x) { access(x); Splay(x); downs(x); } int find_root(int x) { access(x); Splay(x); while (l[x]) { down(x); x = l[x]; } Splay(x); return x; } int split(int x, int y) { make_root(x); if (find_root(y) != x) return -1; access(y); Splay(y); return y; } void cut(int x, int y) {//连和断的时候都要判断连通 make_root(x); if (find_root(y) != x) return ; access(y); Splay(y); l[y] = 0; fa[x] = 0; } void link(int x, int y) { make_root(x); if (find_root(y) != x) fa[x] = y; } ll gcd(ll x, ll y) { if (!y) return x; return gcd(y, x % y); } void write(ll x, ll y) { ll GCD = gcd(x, y); x /= GCD; y /= GCD; printf("%lld/%lld\n", x, y); } int main() { scanf("%d %d", &n, &m); for (int i = 1; i <= n; i++) scanf("%d", &val[i]), sz[i] = 1, sum[i] = lsum[i] = rsum[i] = ans[i] = val[i]; for (int i = 1; i < n; i++) { scanf("%d %d", &x, &y); link(x, y); } while (m--) { scanf("%d %d %d", &op, &x, &y); if (op == 1) { cut(x, y); continue; } if (op == 2) { link(x, y); continue; } if (op == 3) { scanf("%d", &d); if (find_root(x) != find_root(y)) continue;//记得操作前要判断是否连通 x = split(x, y); downa(x, d); continue; } if (op == 4) { if (find_root(x) != find_root(y)) {printf("-1\n");continue;} x = split(x, y); write(ans[x], 1ll * sz[x] * (sz[x] + 1) / 2); continue; } } return 0; }

__EOF__

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