【ybt金牌導航5-4-4】【luogu P4842】城市旅行
城市旅行
題目連結:ybt金牌導航5-4-4 / luogu P4842
題目大意
給你一棵樹,要你維護一些操作:
刪除某條邊(如果兩點間不聯通就不管)
新增某條邊(如果兩點間已聯通就不管)
給某條路徑上的點點權加一個值(如果兩點不連通就不管)
詢問某條路徑上任選兩個點,這兩個點之間路徑的權值和的期望。(如果兩點不連通就輸出 -1)
思路
看到加邊刪邊找路徑,自然想到 LCT。
然後我們考慮如何維護輸出的值。
那容易想到,我們可以補考慮選的點,而是考慮一個點,有多少個路徑會經過它。
那容易想到對於長度為 \(sz\) 的路徑,對於第 \(i\) 個點,有 \(i\times(sz-i+1)\) 個路徑經過了它。(左右各選一個)
那它的貢獻就是 \(i\times(sz-i+1)\times a_i\)
那總貢獻就是:\(\dfrac{\sum\limits_{i=1}^{sz}i\times(sz-i+1)\times a_i}{C_{sz+1}^2}\)
(下面就是 \(C_{sz+1}^2\) 因為選的兩個點可以是同一個點)
那我們要維護的就是它了,分母很好搞,直接每次算就行,問題是分子。
那我們考慮 DP,已經求出了左右子樹,要怎麼搞到它。
我們設左子樹的大小是 \(b_0\),然後序列是 \(b_1,b_2,...,b_{b_0}\),右子樹大小是 \(c_0\),序列是 \(c_1,c_2,...,c_{c_0}\)。
那對於左子樹裡面的第 \(i\) 個點,它在左子樹裡面的貢獻就是 \(i\times(b_0-i+1)\times b_i\)
那左子樹的貢獻就是它原本的貢獻加上 \((c_0+1)\times\sum\limits_{i=1}^{b_0}i\times b_i\)
那我們發現右邊的部分(\(\sum\limits_{i=1}^{b_0}i\times b_i\))我們也可以 DP,我是用 \(lsum\) 陣列記錄,這裡就不講了,不會的自己看程式碼。
那接著右子樹用同樣的方法:
原本:\(i\times(c_0-i+1)\times c_i\)
現在:\((b_0+1+i)\times(b_0+c_0+1-(b_0+1+i)+1)\times c_i\)
\(=(b_0+1+i)\times(c_0-i+1)\times c_i\)
差:\((b_0+1)\times(c_0-i+1)\times c_i\)
那左子樹的貢獻就是它原本的貢獻加上 \((b_0+1)\times\sum\limits_{i=1}^{b_0}(c_0-i+1)\times c_i\)
然後右邊部分(\(\sum\limits_{i=1}^{b_0}(c_0-i+1)\times c_i\))繼續 DP,我是用 \(rsum\) 陣列記錄。
接著就是新的點,那這個其實容易,就直接暴力算:\(a\times(b_0+1)\times(c_0+1)\)。(記得加一)
那查詢我們就搞定了,接著,就是修改了。(加邊刪邊就是普通 LCT,不用搞)
那我們也是懶標記,那每次要怎麼改呢?
首先權值就普通的加,權值和就加上它乘大小。
接著是 \(lsum,rsum\),容易看到你每個數每加 \(x\),值就會多 \(x+2x+3x+4x+...\),那就是 \(x\times (1+sz)\times sz / 2\)
那接著就是 \(ans\),即期望的分子,那我們可以列出式子:\(ans+=\sum\limits_{i=1}^{sz}i\times(sz-i+1)\times d\)
然後我們由化簡可以得到 \(ans+=\dfrac{sz(sz+1)(sz+2)}{6}\times d\)
然後就可以搞啦!
化簡過程
知道的可以不看。
要搞的東西:\(\sum\limits_{i=1}^{sz}i\times(sz-i+1)=\dfrac{sz(sz+1)(sz+2)}{6}\)
首先考慮讓其中一項固定:
\(\sum\limits_{i=1}^{sz}i\times(sz-i+1)=\sum\limits_{i=1}^{sz}i\times sz-\sum\limits_{i=1}^{sz}i\times(i-1)\)
然後右邊部分考慮去括號:\(\sum\limits_{i=1}^{sz}i\times sz-\sum\limits_{i=1}^{sz}(i^2-i)\)
分別拿出來:\(sz\times\sum\limits_{i=1}^{sz}i-\sum\limits_{i=1}^{sz}i^2+\sum\limits_{i=1}^{sz}i\)
然後都可以去掉 \(\sum\):\(sz\times\frac{(sz+1)\times sz}{2}-\frac{sz(sz+1)(2\times sz+1)}{6}+\frac{(sz+1)\times sz}{2}\)
合併一下:\(\frac{3(sz+1)(sz+1)sz}{6}-\frac{sz(sz+1)(2sz+1)}{6}\)
\(\frac{(3sz+3)(sz+1)sz}{6}-\frac{(2sz+1)(sz+1)sz}{6}\)
\(\frac{(sz+2)(sz+1)sz}{6}\)
然後就好啦!
可能有人(指我自己)會不知道為什麼 \(\sum\limits_{i=1}^{sz}i^2=\dfrac{sz(sz+1)(2sz+1)}{6}\)
然後這裡也講講,這個是用立方差來搞的。
\(x^3-(x-1)^3=x^3-(x^3-3x^2+3x-1)=3x^2-3x+1\)
然後根據這個,我們把 \((n^3-(n-1)^3)+((n-1)^3-(n-2)^3)+...+(2^3-1^3)\) 每個都轉。
那互相消掉,就是 \(n^3-1^3=(3n^2-3n+1)+(3(n-1)^2-3(n-1)+1)+...+(3\times2^2-3\times2+1)\)
拆開:\(n^3-1=3n^2+3(n-1)^2+...+3\times2^2-(3n+3(n-1)+...+3\times2+(n-1))\)
然後繼續搞:\(n^3-1=3(n^2+(n-1)^2+...+2^2)-3(n+(n-1)+...+2)+(n-1)\)
移項:\(3(n^2+(n-1)^2+...+2^2+1^2)=n^3-1-(n-1)+\frac{3(n+2)(n-1)}{2}+3\times1^2\)
\(3(n^2+(n-1)^2+...+2^2+1^2)=n^3-n+3+\frac{3(n+2)(n-1)}{2}\)
\((n^2+(n-1)^2+...+2^2+1^2)=\frac{2n^3-2n+6+3(n+2)(n-1)}{6}\)
\(=\frac{2n^3-2n+6+3(n^2+n-2)}{6}=\frac{2n^3+3n^2+n}{6}=\frac{n(2n^2+3n+1)}{6}=\frac{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;
}