正题
luogu 4842
金牌导航 LCT-3
题目大意
给你一棵树,让你进行一些操作:
1.删除一条边
2.连接一条边
3.给一条路径上的点加上x
4.给出一条路径,在该路径选取两个点,求这两个点之间路径的权值和的期望值
解题思路
该树可以用LCT维护
因为答案用分数表示,且计算过程中所有路径的权值和不会大于longlong,所以维护路径权值和然后除以总方案数即可
对于每个节点,维护以下信息
1.s:子树内的路径权值和
2.sz:子树大小
3.sum:子树的点权和
4.num:作为左/右子树时的贡献(每个节点被计算的次数不同)
对于计算一颗子树的s
左子树会被计算szrs+1sz_{rs}+1szrs+1遍,右子树会被计算szls+1sz_{ls}+1szls+1遍,还有当前节点会被计算szls×szrssz_{ls}\times sz_{rs}szls×szrs遍
那么有sx=sls+srs+(szls+1)×numrs,1+(szrs+1)×numls,0+vx×(szls+1)∗(szrs+1)s_x = s_{ls} + s_{rs} + (sz_{ls} + 1) \times num_{rs,1} + (sz_{rs} + 1) \times num_{ls,0} + v_x \times (sz_{ls} + 1) * (sz_{rs} + 1)sx=sls+srs+(szls+1)×numrs,1+(szrs+1)×numls,0+vx×(szls+1)∗(szrs+1)
对于计算num,当作为左子树时,左子树的贡献不变,而每一次计算左子树时右子树和当前点也会被计算,所以要加上(szls+1)×(sumrs+vx)(sz_ls+1)\times (sum_rs + v_x)(szls+1)×(sumrs+vx),作为右子树时同理
其他信息就很好维护了,对此建立LCT即可
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define N 50010
using namespace std;
ll n, m, x, y, z, nm[N];
struct LCT
{#define ls son[x][0]#define rs son[x][1]ll v[N], s[N], p[N], pn[N], fa[N];ll sz[N], sum[N], num[N][2], son[N][2];bool NR(ll x){return fa[x] && (son[fa[x]][0] == x || son[fa[x]][1] == x);}bool IRS(ll x){return son[fa[x]][1] == x;}void push_up(ll x){s[x] = s[ls] + s[rs] + (sz[ls] + 1) * num[rs][1] + (sz[rs] + 1) * num[ls][0] + v[x] * (sz[ls] + 1) * (sz[rs] + 1);num[x][0] = num[ls][0] + (v[x] + sum[rs]) * (sz[ls] + 1) + num[rs][0];num[x][1] = num[rs][1] + (v[x] + sum[ls]) * (sz[rs] + 1) + num[ls][1];sum[x] = sum[ls] + sum[rs] + v[x];sz[x] = sz[ls] + sz[rs] + 1;return;}void pushr(ll x){swap(ls, rs);swap(num[x][0], num[x][1]);//翻转时作为左/右子树的贡献会改变p[x] ^= 1;return;}void push_add(ll x, ll y){s[x] += nm[sz[x]] * y;//nm为所有方案下的点数和,在下文计算到num[x][0] += sz[x] * (sz[x] + 1) / 2 * y;//在维护时会计算到的次数num[x][1] += sz[x] * (sz[x] + 1) / 2 * y;sum[x] += sz[x] * y;v[x] += y;pn[x] += y;return; }void push_down(ll x){if (p[x]){if (ls) pushr(ls);if (rs) pushr(rs);p[x] = 0;}if (pn[x]){if (ls) push_add(ls, pn[x]);if (rs) push_add(rs, pn[x]);pn[x] = 0;}return;}void push_hall(ll x){if (NR(x)) push_hall(fa[x]);push_down(x);return;}void rotate(ll x){ll y = fa[x], z = fa[y], k = IRS(x), g = son[x][!k];if (NR(y)) son[z][IRS(y)] = x;if (g) fa[g] = y;son[x][!k] = y;son[y][k] = g;fa[y] = x;fa[x] = z;push_up(y);return;}void Splay(ll x){push_hall(x);while(NR(x)){if (NR(fa[x])){if (IRS(x) == IRS(fa[x])) rotate(fa[x]);else rotate(x);}rotate(x);}push_up(x);return;}void access(ll x){for (ll y = 0; x; x = fa[y = x])Splay(x), rs = y, push_up(x);return;}void make_root(ll x){access(x);Splay(x);pushr(x);return;}ll find_root(ll x){access(x);Splay(x);while(ls) push_down(x), x = ls;Splay(x);return x;}bool Split(ll x, ll y){make_root(x);if (find_root(y) != x) return 0; Splay(y);return 1;}void link(ll x, ll y){make_root(x);if (find_root(y) != x) fa[x] = y;return;}void cut(ll x, ll y){make_root(x);if (find_root(y) == x && fa[y] == x && !son[y][0]){fa[y] = rs = 0;push_up(x);}return;}
}T;
ll gcd(ll x, ll y)
{if (!y) return x;return gcd(y, x % y);
}
void solve(ll x, ll y)
{ll z = gcd(x, y);printf("%lld/%lld\n", x / z, y / z);return;
}
int main()
{scanf("%lld%lld", &n, &m);nm[1] = 1;for (ll i = 2; i <= n; ++i)nm[i] = nm[i - 1] + i * (i + 1) / 2;for (ll i = 1; i <= n; ++i)scanf("%lld", &T.v[i]);for (ll i = 1; i < n; ++i){scanf("%lld%lld", &x, &y);T.link(x, y);}while(m--){scanf("%lld%lld%lld", &z, &x, &y);if (z == 1) T.cut(x, y);else if (z == 2) T.link(x, y);else if (z == 3){scanf("%lld", &z);if (T.Split(x, y)) T.push_add(y, z);}else{if (!T.Split(x, y)) puts("-1");else solve(T.s[y], T.sz[y] * (T.sz[y] + 1) / 2);}}return 0;
}