【bzoj3091】城市旅行 LCT区间合并
题目描述
输入
输出
样例输入
4 5
1 3 2 5
1 2
1 3
2 4
4 2 4
1 2 4
2 3 4
3 1 4 1
4 1 4
样例输出
16/3
6/1
题解
LCT区间合并
前三个操作都是LCT的基本操作,可以LCT水过;重点在于第四个操作。
考虑一个长度为n的序列,它的子区间个数为$\sum\limits_{i=1}^ni=\frac{n(n-1)}2$,只需要维护每个子区间的长度之和即可。
考虑如果已经知道了左右子树的信息以及当前节点信息,如何更新当前子树的信息。需要解决区间合并问题。
答案除了原来两子树答案之和以外,考虑1~n序列中的第i个点,它对答案的贡献是 左边个数*右边个数$i(n-i+1)$ ,而在合并后它右边的元素多了$si[rs]+1$,故答案增加了$\sum\limits_{i\in ls}rank[i]*w[i]$。所以需要记录一个数组$lv[]$,它表示 子树中每个点的权值*该点在子树中从左向右的排名(正排名) 之和。
对于右边的点,同理,记录一个$rv[]$表示 子树中每个点的权值*该点在子树中从右向左的排名(逆排名) 之和。
于是就可以使用这两个数组更新总答案。
然后考虑怎么更新这两个数组,$lv$数组pushup时只涉及到当前节点和右子树的节点,每个节点的排名增加了$si[ls]+1$,所以$lv$除了两子树的$lv$之和外还要加上$(w[x]+sum[rs])*(si[ls]+1)$。
所以对于每个节点,维护$si,w,sum,lv,rv,tv$,分别为子树大小、当前节点权值、当前子树权值和、权值*正排名、权值*逆排名、答案(子区间总长)。
然后思考add&pushdown怎样进行:$sum$包含$si$个节点的贡献,所以加上$si*tag$;$lv,rv$包含$\sum\limits_{i=1}^{si}i=\frac{si(si+1)}2$个节点的贡献,所以加上$\frac{si(si+1)*tag}2$;$tv$包含$\sum\limits_{i=1}^n\sum\limits_{j=1}^ij=\frac{n(n+1)(n+2)}6$的贡献,所以加上$\frac{n(n+1)(n+2)*tag}6$。
知道了pushup和add&pushdown怎么写以后本题就水了,直接split出链后取出$tv$,与$\frac{si(si+1)}2$作比即为答案。
但 是 有 一 点 需 要 注 意 , 写 findroot 时 必 须 找 Splay tree 的 根 , 而 不 是 原 树 根 , 否 则 会 因 为 某 些 玄 学 原 因 而 无 限 TLE !
感觉说了这么多也没有直接看代码来的直白~
#include <cstdio> #include <cstring> #include <algorithm> #define N 50010 using namespace std; typedef long long ll; int fa[N] , c[2][N] , rev[N]; ll si[N] , w[N] , sum[N] , lv[N] , rv[N] , tv[N] , tag[N]; void rever(int x) { swap(c[0][x] , c[1][x]) , swap(lv[x] , rv[x]) , rev[x] ^= 1; } void add(int x , ll a) { w[x] += a , sum[x] += a * si[x]; lv[x] += a * si[x] * (si[x] + 1) / 2; rv[x] += a * si[x] * (si[x] + 1) / 2; tv[x] += a * si[x] * (si[x] + 1) * (si[x] + 2) / 6; tag[x] += a; } void pushup(int x) { int l = c[0][x] , r = c[1][x]; si[x] = si[l] + si[r] + 1; sum[x] = sum[l] + sum[r] + w[x]; lv[x] = lv[l] + lv[r] + (w[x] + sum[r]) * (si[l] + 1); rv[x] = rv[l] + rv[r] + (w[x] + sum[l]) * (si[r] + 1); tv[x] = tv[l] + tv[r] + lv[l] * (si[r] + 1) + rv[r] * (si[l] + 1) + w[x] * (si[l] + 1) * (si[r] + 1); } void pushdown(int x) { int l = c[0][x] , r = c[1][x]; if(rev[x]) rever(l) , rever(r) , rev[x] = 0; if(tag[x]) add(l , tag[x]) , add(r , tag[x]) , tag[x] = 0; } bool isroot(int x) { return c[0][fa[x]] != x && c[1][fa[x]] != x; } void update(int x) { if(!isroot(x)) update(fa[x]); pushdown(x); } void rotate(int x) { int y = fa[x] , z = fa[y] , l = (c[1][y] == x) , r = l ^ 1; if(!isroot(y)) c[c[1][z] == y][z] = x; fa[x] = z , fa[y] = x , fa[c[r][x]] = y , c[l][y] = c[r][x] , c[r][x] = y; pushup(y) , pushup(x); } void splay(int x) { update(x); while(!isroot(x)) { int y = fa[x] , z = fa[y]; if(!isroot(y)) { if((c[0][y] == x) ^ (c[0][z] == y)) rotate(x); else rotate(y); } rotate(x); } } void access(int x) { int t = 0; while(x) splay(x) , c[1][x] = t , pushup(x) , t = x , x = fa[x]; } int findroot(int x) { while(fa[x]) x = fa[x]; return x; } void makeroot(int x) { access(x) , splay(x) , rever(x); } void link(int x , int y) { if(findroot(x) != findroot(y)) makeroot(x) , fa[x] = y; } void split(int x , int y) { makeroot(x) , access(y) , splay(y); } void cut(int x , int y) { split(x , y); if(fa[x] == y) fa[x] = c[0][y] = 0 , pushup(y); } ll gcd(ll a , ll b) { return b ? gcd(b , a % b) : a; } int main() { int n , m , i , opt , x , y; ll z , t; scanf("%d%d" , &n , &m); for(i = 1 ; i <= n ; i ++ ) scanf("%lld" , &w[i]) , pushup(i); for(i = 1 ; i < n ; i ++ ) scanf("%d%d" , &x , &y) , link(x , y); while(m -- ) { scanf("%d%d%d" , &opt , &x , &y); if(opt == 1) cut(x , y); else if(opt == 2) link(x , y); else if(opt == 3) { scanf("%lld" , &z); if(findroot(x) == findroot(y)) split(x , y) , add(y , z); } else { if(findroot(x) == findroot(y)) { split(x , y) , z = tv[y] , t = si[y] * (si[y] + 1) / 2; printf("%lld/%lld\n" , z / gcd(z , t) , t / gcd(z , t)); } else puts("-1"); } } return 0; }