动态树学习笔记

数据结构使我头秃

动态树,又名Link-Cut-Tree,简称LCT

我们要维护一个森林,这个森林里面有很多有根树

然后说一下定义,定义就是实边和虚边了

一个点和它的儿子中只能有一条实边,这些实边串起来后用splay维护,头结点的父亲就是路径中最浅节点的父亲
然而怎么判断Splay的根呢,根的父亲还是有的(这是虚边的表示方法),但是根的父亲的左右儿子都不是它

然后有一些操作

1 Access(x)

这里构造一条x到根的路径
1.如果x和儿子有实边,就断掉,方法是x转到根,然后断掉右儿子
2.然后找到路径的父亲,把这个父亲转到根,然后断掉右儿子,换成x所在的splay
3.如果x和根不在一条路径里,再执行2

2 FindRoot(x)

找到x的根
先Access(x),然后找到splay最左节点

3.MakeRoot(x)

先Access(x),然后把这棵树的路径反向

4.Link(x,y)

先把x变成根,然后把y的左儿子变成x

5.Cut(x,y)

把x变成根,然后Access(y),把y旋到根,断掉y的左儿子和x的父亲

6.Select(x,y)

得到x到y之间的路径
把x变成根然后Access(y),他们之间的信息就在一棵平衡树里了

BZOJ 2049: [Sdoi2008]Cave 洞穴勘测

Time Limit: 10 Sec Memory Limit: 259 MB
Submit: 10014 Solved: 4849
[Submit][Status][Discuss]

Description

辉辉热衷于洞穴勘测。某天,他按照地图来到了一片被标记为JSZX的洞穴群地区。经过初步勘测,辉辉发现这片区域由n个洞穴(分别编号为1到n)以及若干通道组成,并且每条通道连接了恰好两个洞穴。假如两个洞穴可以通过一条或者多条通道按一定顺序连接起来,那么这两个洞穴就是连通的,按顺序连接在一起的这些通道则被称之为这两个洞穴之间的一条路径。洞穴都十分坚固无法破坏,然而通道不太稳定,时常因为外界影响而发生改变,比如,根据有关仪器的监测结果,123号洞穴和127号洞穴之间有时会出现一条通道,有时这条通道又会因为某种稀奇古怪的原因被毁。辉辉有一台监测仪器可以实时将通道的每一次改变状况在辉辉手边的终端机上显示:如果监测到洞穴u和洞穴v之间出现了一条通道,终端机上会显示一条指令 Connect u v 如果监测到洞穴u和洞穴v之间的通道被毁,终端机上会显示一条指令 Destroy u v 经过长期的艰苦卓绝的手工推算,辉辉发现一个奇怪的现象:无论通道怎么改变,任意时刻任意两个洞穴之间至多只有一条路径。因而,辉辉坚信这是由于某种本质规律的支配导致的。因而,辉辉更加夜以继日地坚守在终端机之前,试图通过通道的改变情况来研究这条本质规律。然而,终于有一天,辉辉在堆积成山的演算纸中崩溃了……他把终端机往地面一砸(终端机也足够坚固无法破坏),转而求助于你,说道:“你老兄把这程序写写吧”。辉辉希望能随时通过终端机发出指令 Query u v,向监测仪询问此时洞穴u和洞穴v是否连通。现在你要为他编写程序回答每一次询问。已知在第一条指令显示之前,JSZX洞穴群中没有任何通道存在。

Input

第一行为两个正整数n和m,分别表示洞穴的个数和终端机上出现过的指令的个数。以下m行,依次表示终端机上出现的各条指令。每行开头是一个表示指令种类的字符串s("Connect”、”Destroy”或者”Query”,区分大小写),之后有两个整数u和v (1≤u, v≤n且u≠v) 分别表示两个洞穴的编号。

Output

对每个Query指令,输出洞穴u和洞穴v是否互相连通:是输出”Yes”,否则输出”No”。(不含双引号)

Sample Input

样例输入1 cave.in

200 5
Query 123 127
Connect 123 127
Query 123 127
Destroy 127 123
Query 123 127

样例输入2 cave.in

3 5
Connect 1 2
Connect 3 1
Query 2 3
Destroy 1 3
Query 2 3

Sample Output

样例输出1 cave.out

No
Yes
No

样例输出2 cave.out

Yes
No

HINT

数据说明 10%的数据满足n≤1000, m≤20000 20%的数据满足n≤2000, m≤40000 30%的数据满足n≤3000, m≤60000 40%的数据满足n≤4000, m≤80000 50%的数据满足n≤5000, m≤100000 60%的数据满足n≤6000, m≤120000 70%的数据满足n≤7000, m≤140000 80%的数据满足n≤8000, m≤160000 90%的数据满足n≤9000, m≤180000 100%的数据满足n≤10000, m≤200000 保证所有Destroy指令将摧毁的是一条存在的通道本题输入、输出规模比较大,建议c\c++选手使用scanf和printf进行I\O操作以免超时

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAXN 1000005
//#define ivorysi
using namespace std;
typedef long long ll;
char s[25];
int n,m;
struct node {
    node *fa,*lc,*rc;
    int rev;
    void Reverse() {
    swap(lc,rc);
    rev ^= 1;
    }
}*tr[MAXN];
node *que[MAXN];
int tot;
bool isRoot(node *u) {
    if(u->fa == NULL) return 1;
    return u->fa->lc != u && u->fa->rc != u;
}
void push_down(node *u) {
    if(u->rev) {
    if(u->lc) u->lc->Reverse();
    if(u->rc) u->rc->Reverse();
    u->rev = 0;
    }
}
int which(node *u) {
    return u->fa->rc == u;
}
void Rotate(node *u) {
    node *v = u->fa , *w = v->fa;
    node *b = u == v->lc ? u->rc : u->lc;
    
    if(w && !isRoot(v)) (v == w->lc ? w->lc : w->rc) = u;
    v->fa = u; u->fa = w;
    if(b) b->fa = v;
    if(u == v->lc) {v->lc = b; u->rc = v;}
    else {v->rc = b;u->lc = v;}
}
void Splay(node *u) {
    tot = 0;node *x;
    for(x = u ; !isRoot(x) ; x = x->fa) {
    que[++tot] = x;
    }
    que[++tot] = x;
    for(int i = tot ; i >= 1 ; --i) {
    push_down(que[i]);
    }
    while(!isRoot(u)) {
    if(!isRoot(u->fa)) {
        if(which(u) == which(u->fa)) Rotate(u->fa);
        else Rotate(u);
    }
    Rotate(u);
    }
}
void Access(node *u) {
    for(node *v = NULL ; u != NULL;v = u , u = u->fa) {
    Splay(u); u->rc = v;
    if(v) v->fa = u;
    }
}
node* Findroot(node *u) {
    Access(u);
    Splay(u);
    push_down(u);
    while(u->lc) {
    u = u->lc;
    push_down(u);
    }
    Splay(u);
    return u;
}
void Makeroot(node *u) {
    Access(u);Splay(u);
    u->Reverse();
}
void Link(node *u,node *v) {
    Makeroot(u);
    u->fa = v;
}
void Cut(node *u,node *v) {
    Makeroot(u);Access(v);Splay(v);
    v->lc = 0;u->fa = 0;
}

int main() {
#ifdef ivorysi
    freopen("f1.in","r",stdin);
#endif
    scanf("%d%d",&n,&m);
    for(int i = 1 ; i <= n ; ++i) {
    tr[i] = new node;
    tr[i]->lc = tr[i]->rc = tr[i]->fa = NULL;
    tr[i]->rev = 0;
    }
    int u,v;
    for(int i = 1 ; i <= m ; ++i) {
    scanf("%s",s + 1);
    scanf("%d%d",&u,&v);
    if(s[1] == 'C') {
        Link(tr[u],tr[v]);
    }
    else if(s[1] == 'D') {
        Cut(tr[u],tr[v]);
    }
    else if(s[1] == 'Q') {
        if(Findroot(tr[u]) == Findroot(tr[v])) puts("Yes");
        else puts("No");
    }
    }
}

BZOJ 2002 [Hnoi2010]Bounce 弹飞绵羊

Time Limit: 10 Sec Memory Limit: 259 MB
Submit: 12552 Solved: 6374
[Submit][Status][Discuss]

Description

某天,Lostmonkey发明了一种超级弹力装置,为了在他的绵羊朋友面前显摆,他邀请小绵羊一起玩个游戏。游戏一开始,Lostmonkey在地上沿着一条直线摆上n个装置,每个装置设定初始弹力系数ki,当绵羊达到第i个装置时,它会往后弹ki步,达到第i+ki个装置,若不存在第i+ki个装置,则绵羊被弹飞。绵羊想知道当它从第i个装置起步时,被弹几次后会被弹飞。为了使得游戏更有趣,Lostmonkey可以修改某个弹力装置的弹力系数,任何时候弹力系数均为正整数。

Input

第一行包含一个整数n,表示地上有n个装置,装置的编号从0到n-1,接下来一行有n个正整数,依次为那n个装置的初始弹力系数。第三行有一个正整数m,接下来m行每行至少有两个数i、j,若i=1,你要输出从j出发被弹几次后被弹飞,若i=2则还会再输入一个正整数k,表示第j个弹力装置的系数被修改成k。对于20%的数据n,m<=10000,对于100%的数据n<=200000,m<=100000

Output

对于每个i=1的情况,你都要输出一个需要的步数,占一行。

Sample Input

4
1 2 1 1
3
1 1
2 1 1
1 1

Sample Output

2
3

题解

这道题……需要重写一下splay让它支持固定父亲和直接旋到根,比较烦啊……
我们给跳出去建一个点,每个点向弹跳距离后的那个点连一条边,会发现这是很多森林,然后用LCT维护就可以了
2018.2.13更新
其实可以不用这样很麻烦的splay操作,因为Access已经保证了这个连向根的点没有任何的偏爱边,那么根所在的splay的大小就是我们想要的答案

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAXN 1000005
#define rc(x) tr[x].rc
#define lc(x) tr[x].lc
#define rev(x) tr[x].rev
#define fa(x) tr[x].fa
#define size(x) tr[x].siz
//#define ivorysi
using namespace std;
typedef long long ll;
char s[25];
int n,m;
struct node {
    int fa,lc,rc,rev,siz;
    void Reverse() {
    swap(lc,rc);
    rev ^= 1;
    }
}tr[MAXN];
int jp[MAXN],que[MAXN],tot;
bool isRoot(int x) {
    if(fa(x) == 0) return 1;
    return lc(fa(x)) != x && rc(fa(x)) != x;
}
int which(int x) {
    return rc(fa(x)) == x;
}
void Update(int u) {
    if(u == 0) return;
    size(u) = size(lc(u)) + size(rc(u)) + 1;
}
void Push_down(int u) {
    if(rev(u)) {
    if(lc(u)) tr[lc(u)].Reverse();
    if(rc(u)) tr[rc(u)].Reverse();
    rev(u) = 0;
    }
}

void Rotate(int u) {
    int v = fa(u) , w = fa(v);
    int b = lc(v) == u ? rc(u) : lc(u);
    if(w && !isRoot(v)) (v == lc(w) ? lc(w) : rc(w)) = u;
    fa(u) = w;fa(v) = u;
    if(b) fa(b) = v;
    if(u == lc(v)) {lc(v) = b;rc(u) = v;}
    else {rc(v) = b;lc(u) = v;}
    Update(v);
}


void Splay(int u,int tar,int on) {
    tot = 0;int x;
    for(x = u ; (on ? (!isRoot(x)):(fa(x) != tar))  ; x = fa(x)) {
    que[++tot] = x;
    }
    que[++tot] = x;
    for(int i = tot ; i >= 1 ; --i) {
    Push_down(que[i]);
    }
    while((on ? (!isRoot(u)):(fa(u) != tar))) {
    if((on ? (!isRoot(fa(u))):(fa(fa(u)) != tar))) {
        if(which(fa(u)) == which(u)) Rotate(fa(u));
        else Rotate(u);
    }
    Rotate(u);
    }
    Update(u);
}
void Access(int x) {
    for(int y = 0 ; x ; y = x , x = fa(x)) {
    Splay(x,0,1);rc(x) = y;Update(x);
    if(y) {fa(y) = x;}
    }
}
int Findroot(int x) {
    Access(x);Splay(x,0,1);
    Push_down(x);
    while(lc(x)) {
    x = lc(x);
    Push_down(x);
    }
    return x;
}
void Makeroot(int x) {
    Access(x);Splay(x,0,1);
    tr[x].Reverse();
}
void Link(int x,int y) {
    Makeroot(x);fa(x) = y;
}
void Cut(int x,int y) {
    Makeroot(x);
    Access(y);
    Splay(y,0,1);
    lc(y) = 0;fa(x) = 0;
    Update(y);
}
int Select(int x,int y) {
    Makeroot(x);Access(y);
    Splay(x,0,1);
    Splay(y,x,0);
    return tr[lc(y)].siz + 1;
}
int main() {
#ifdef ivorysi
    freopen("f1.in","r",stdin);
#endif
    scanf("%d",&n);
    for(int i = 1 ; i <= n ; ++i) {
    scanf("%d",&jp[i]);
    tr[i].siz = 1;
    }
    tr[n + 1].siz = 1;
    for(int i = 1 ; i <= n ; ++i) {
    Link(i,(i + jp[i] <= n ? i + jp[i] : n + 1));
    }
    int op;
    int u,k;
    scanf("%d",&m);
    while(m--) {
    scanf("%d%d",&op,&u);
    ++u;
    if(op == 1) {
        printf("%d\n",Select(u,n + 1));
    }
    else if(op == 2) {
        scanf("%d",&k);
        Cut(u,(u + jp[u] <= n ? u + jp[u] : n + 1));
        jp[u] = k;
        Link(u,(u + jp[u] <= n ? u + jp[u] : n + 1));
        
    }
    }
    return 0;
}

BZOJ 3669: [Noi2014]魔法森林

Time Limit: 30 Sec Memory Limit: 512 MB
Submit: 3156 Solved: 1997
[Submit][Status][Discuss]

Description

为了得到书法大家的真传,小E同学下定决心去拜访住在魔法森林中的隐士。魔法森林可以被看成一个包含个N节点M条边的无向图,节点标号为1..N,边标号为1..M。初始时小E同学在号节点1,隐士则住在号节点N。小E需要通过这一片魔法森林,才能够拜访到隐士。
魔法森林中居住了一些妖怪。每当有人经过一条边的时候,这条边上的妖怪就会对其发起攻击。幸运的是,在号节点住着两种守护精灵:A型守护精灵与B型守护精灵。小E可以借助它们的力量,达到自己的目的。
只要小E带上足够多的守护精灵,妖怪们就不会发起攻击了。具体来说,无向图中的每一条边Ei包含两个权值Ai与Bi。若身上携带的A型守护精灵个数不少于Ai,且B型守护精灵个数不少于Bi,这条边上的妖怪就不会对通过这条边的人发起攻击。当且仅当通过这片魔法森林的过程中没有任意一条边的妖怪向小E发起攻击,他才能成功找到隐士。
由于携带守护精灵是一件非常麻烦的事,小E想要知道,要能够成功拜访到隐士,最少需要携带守护精灵的总个数。守护精灵的总个数为A型守护精灵的个数与B型守护精灵的个数之和。

Input

第1行包含两个整数N,M,表示无向图共有N个节点,M条边。 接下来M行,第行包含4个正整数Xi,Yi,Ai,Bi,描述第i条无向边。其中Xi与Yi为该边两个端点的标号,Ai与Bi的含义如题所述。 注意数据中可能包含重边与自环。

Output

输出一行一个整数:如果小E可以成功拜访到隐士,输出小E最少需要携带的守护精灵的总个数;如果无论如何小E都无法拜访到隐士,输出“-1”(不含引号)。

Sample Input

【输入样例1】

4 5
1 2 19 1
2 3 8 12
2 4 12 15
1 3 17 8
3 4 1 17

题解

我们可以先想到给A排序,然后一条条加边,用并查集维护连通性
然而我们麻烦的是有一条新的边加入时,该删哪条边,或者这条新的边该不该加进去
我们发现[1,N]的路径一定是一个生成树,然后用LCT求一下这条路径的最大值,然后删掉最大值所在的边,然后加进去这条边
我们可以对每个边新建一个点,用点权代替边权

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAXN 100005
#define fa(x) tr[x].fa
#define lc(x) tr[x].lc
#define rc(x) tr[x].rc
#define rev(x) tr[x].rev
#define val(x) tr[x].val
#define pos(x) tr[x].pos
//#define ivorysi
using namespace std;
typedef long long ll;
struct node {
    int lc,rc,fa,rev;
    int val,pos;
    void Reverse() {
        swap(lc,rc);
        rev ^= 1;
    }
}tr[MAXN*2];
int que[MAXN*2],tot;
struct data {
    int u,v,a,b;
    bool operator < (const data &rhs) const {
        return a < rhs.a || (a == rhs.a && b < rhs.b);
    }
}Eg[MAXN];
bool isRoot(int u) {
    if(fa(u) == 0) return true;
    return lc(fa(u)) != u && rc(fa(u)) != u;
}
void Push_down(int u) {
    if(rev(u)) {
        if(lc(u)) tr[lc(u)].Reverse();
        if(rc(u)) tr[rc(u)].Reverse();
        rev(u) = 0;
    }
}
void Update(int u) {
    pos(u) = u;
    if(val(pos(lc(u))) > val(pos(u)))  pos(u) = pos(lc(u));
    if(val(pos(rc(u))) > val(pos(u)))  pos(u) = pos(rc(u));
}
void Rotate(int u) {
    int v = fa(u) , w = fa(v);
    int b = lc(v) == u ? rc(u) : lc(u);
    if(w && !isRoot(v)) (lc(w) == v ? lc(w) : rc(w)) = u;
    fa(u) = w;fa(v) = u;
    if(b) fa(b) = v;
    if(u == lc(v)) {lc(v) = b;rc(u) = v;}
    else {rc(v) = b;lc(u) = v;}
    Update(v);
}
int which(int x) {
    return rc(fa(x)) == x;
}
void Splay(int u) {
    int x;tot = 0;
    for(x = u ; !isRoot(x) ; x = fa(x)) {
        que[++tot] = x;
    }
    que[++tot] = x;
    for(int i = tot ; i >= 1 ; --i) {
        Push_down(que[i]);
    }
    while(!isRoot(u)) {
        while(!isRoot(fa(u))) {
            if(which(fa(u)) == which(u)) Rotate(fa(u));
            else Rotate(u);
        }
        Rotate(u);
    }
    Update(u);
}
void Access(int x) {
    for(int y = 0 ; x ; y = x , x = fa(x)) {
        Splay(x);rc(x) = y; Update(x);
        if(y) fa(y) = x;
    }
}
void MakeRoot(int x) {
    Access(x);Splay(x);
    tr[x].Reverse();
}
void Link(int x,int y) {
    MakeRoot(x);
    fa(x) = y;  
}
void Cut(int x,int y) {
    MakeRoot(x);Access(y);Splay(y);
    lc(y) = 0;fa(x) = 0;
    Update(y);
}
int Select(int x,int y) {
    MakeRoot(x);Access(y);Splay(y);
    return pos(y);
}

int n,m,fa[MAXN];
int getfa(int x) {
    return fa[x] == x ? x : fa[x] = getfa(fa[x]);
}
int main() {
#ifdef ivorysi
    freopen("f1.in","r",stdin);
#endif
    scanf("%d%d",&n,&m);
    for(int i = 1 ; i <= m ; ++i) {
        scanf("%d%d%d%d",&Eg[i].u,&Eg[i].v,&Eg[i].a,&Eg[i].b);
    }
    sort(Eg + 1,Eg + m + 1);
    for(int i = 1 ; i <= n ; ++i) {
        fa[i] = i;
    }
    int ans = 10000000;
    for(int i = 1 ; i <= m ; ++i) {
        if(getfa(Eg[i].u) == getfa(Eg[i].v)) {
            int x = Select(Eg[i].u,Eg[i].v);
            if(val(x) > Eg[i].b) {
                Cut(x,Eg[x - n].u);
                Cut(x,Eg[x - n].v);
            }
            else continue;
        }
        else {
            fa[getfa(Eg[i].u)] = getfa(Eg[i].v);
        }
        Link(i + n , Eg[i].u);
        Link(i + n , Eg[i].v);
        val(i + n) = Eg[i].b;
        if(getfa(1) == getfa(n)) ans = min(ans,Eg[i].a + val(Select(1,n)));
    }
    if(getfa(1) != getfa(n)) puts("-1");
    else printf("%d\n",ans);
}

BZOJ 3091: 城市旅行

Time Limit: 10 Sec Memory Limit: 128 MB
Submit: 1887 Solved: 621
[Submit][Status][Discuss]

Description

给出一棵树,每个节点有一个点权,支持4个操作
1 u v 删除u,v之间的边
2 u v 在u,v之间加一条边
3 u v d在u v 路径上每一个点加上一个值d
4 u v 在u,v之间任意选择两个点,这条路径的贡献为这条路径所有节点值的和,求期望,输出分数

题解

前三个看起来还能搞,第四个是个什么玩意
首先这个分数的分母好确定\(size * (size + 1) / 2\)
所以我们只要考虑分子了
是这个序列的值
\(1 \cdot n \cdot a_1 + 2 \cdot (n - 1) \cdot a_2 + ..... + n \cdot 1 \cdot a_n\)
然后我们看左子树有三个值,右子树有两个数

\(a_1\) , \(a_2\) , \(a_3\) , \(a_4\) , \(a_5\) , \(a_6\)
\(1\*3 , 2\*2 , 3\*1 , rt , 1\*2 , 2\*1\)

后来它会变成

\(a_1\) , \(a_2\) , \(a_3\) , \(a_4\) , \(a_5\) , \(a_6\)
\(1\*6 , 2\*5 , 3\*4 , 4\*3 , 5\*2 , 6\*1\)

对于左子树上下作差,那么我们会得到

\(3 * (1 * a_1 + 2 * a_2 + 3 * a_3)\)

这个3就是右儿子的size+1
然后我们对于每个点维护一个
\(lsum = 1 * a_1 + 2 * a_2 ...\)
\(rsum = n * a_1 + (n - 1) * a_2...\)
然后期望就可以维护了

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAXN 50005
#define rc(x) tr[x].rc
#define lc(x) tr[x].lc
#define rev(x) tr[x].rev
#define fa(x) tr[x].fa
#define add(x) tr[x].add
#define val(x) tr[x].val
#define lsum(x) tr[x].lsum
#define rsum(x) tr[x].rsum
#define sum(x) tr[x].sum
#define exp(x) tr[x].exp
#define siz(x) tr[x].siz
//#define ivorysi
using namespace std;
typedef long long ll;

int n,m;
struct node {
    int fa,lc,rc,rev,siz;
    ll add,val,lsum,rsum,exp,sum;
    void Reverse() {
    swap(lsum,rsum);
    swap(lc,rc);
    rev ^= 1;
    }
    void Add(ll x) {
    val += x;
    lsum += 1LL * siz * (siz + 1) / 2 * x;
    rsum += 1LL * siz * (siz + 1) / 2 * x;
    exp += 1LL * siz * (siz + 1) * (siz + 2) / 6 * x;
    sum += 1LL * siz * x;
    add += x;
    }
}tr[MAXN];
int que[MAXN],tot;
void Push_down(int u) {
    if(rev(u)) {
    if(lc(u)) tr[lc(u)].Reverse();
    if(rc(u)) tr[rc(u)].Reverse();
        rev(u) = 0;
    }
    if(add(u) != 0) {
    if(lc(u)) tr[lc(u)].Add(add(u));
    if(rc(u)) tr[rc(u)].Add(add(u));
    add(u) = 0;
    }
}
void Update(int u) {
    exp(u) = exp(lc(u)) + exp(rc(u)) +
    lsum(lc(u)) * (siz(rc(u)) + 1) +
    rsum(rc(u)) * (siz(lc(u)) + 1) +
    val(u) * (siz(lc(u)) + 1) * (siz(rc(u)) + 1);
    lsum(u) = lsum(lc(u)) + val(u) * (siz(lc(u)) + 1) +
    sum(rc(u)) * (siz(lc(u)) + 1) + lsum(rc(u));
    rsum(u) = rsum(rc(u)) + val(u) * (siz(rc(u)) + 1) +
    sum(lc(u)) * (siz(rc(u)) + 1) + rsum(lc(u));
    siz(u) = siz(lc(u)) + siz(rc(u)) + 1;
    sum(u) = sum(lc(u)) + sum(rc(u)) + val(u);
}
bool isRoot(int u) {
    if(fa(u) == 0) return 1;
    return lc(fa(u)) != u && rc(fa(u)) != u;
}
int which(int u) {
    return rc(fa(u)) == u;
}
void Rotate(int u) {
    int v = fa(u) , w = fa(v);
    int b = u == lc(v) ? rc(u) : lc(u);
    if(w && !isRoot(v)) (v == lc(w) ? lc(w) : rc(w)) = u;
    fa(u) = w;fa(v) = u;
    if(b) fa(b) = v;
    if(u == lc(v)) {lc(v) = b; rc(u) = v;}
    else {rc(v) = b;lc(u) = v;}
    Update(v);
}
void Splay(int u) {
    tot = 0;
    int x;
    for(x = u ; !isRoot(x) ; x = fa(x)) {
    que[++tot] = x;
    }
    que[++tot] = x;
    for(int i = tot ; i >= 1 ; --i) {
    Push_down(que[i]);
    }
    while(!isRoot(u)) {
    if(!isRoot(fa(u))) {
        if(which(fa(u)) == which(u)) Rotate(fa(u));
        else Rotate(u);
    }
    Rotate(u);
    }
    Update(u);
}
void Access(int x) {
    for(int y = 0 ; x ; y = x , x = fa(x)) {
    Splay(x);rc(x) = y;Update(x);
    if(y) fa(y) = x;
    }
}
int FindRoot(int x) {
    Access(x);
    Splay(x);
    Push_down(x);
    while(lc(x)) {
    x = lc(x);
    Push_down(x);
    }
    Splay(x);
    return x;
}
void MakeRoot(int x) {
    Access(x);Splay(x);
    tr[x].Reverse();
}
void Cut(int x,int y) {
    MakeRoot(x);Access(y);Splay(y);
    if(lc(y) == x) {
    lc(y) = 0;fa(x) = 0;
    Update(y);
    }
}
void Link(int x,int y) {
    if(FindRoot(x) == FindRoot(y)) return;
    MakeRoot(x);fa(x) = y;
}
void AddVal(int x,int y,ll c) {
    if(FindRoot(x) != FindRoot(y)) return;
    MakeRoot(x);Access(y);Splay(y);
    tr[y].Add(c);
}
ll gcd(ll a,ll b) {
    return b == 0 ? a : gcd(b,a%b);
}
void Query(int x,int y) {
    if(FindRoot(x) != FindRoot(y)) {puts("-1");return;}
    MakeRoot(x);Access(y);Splay(y);
    ll up = exp(y);
    ll down = 1LL * siz(y) * (siz(y) + 1) / 2;
    ll g = gcd(up,down);
    up /= g;down /= g;
    printf("%lld/%lld\n",up,down);
}
ll read() {
    ll res = 0;
    char c = getchar();
    while(c < '0' || c > '9') c = getchar();
    while(c >= '0' && c <= '9') {
    res = res * 10 + c - '0';
    c = getchar();
    }
    return res;
}
int main() {
#ifdef ivorysi
    freopen("f1.in","r",stdin);
#endif
    n = read(); m = read();
    for(int i = 1 ; i <= n ; ++i) {
    tr[i].val = read();
    tr[i].lsum = tr[i].rsum = tr[i].sum = tr[i].exp = tr[i].val;
    tr[i].siz = 1;
    }
    
    int op,u,v;
    ll c;
    for(int i = 1 ; i < n ; ++i) {
    u = read(); v = read();
    Link(u,v);
    }
    while(m--) {
    op = read();
    u = read(); v = read();
    if(op == 1) {
        Cut(u,v);
    }
    else if(op == 2) {
        Link(u,v);
    }
    else if(op == 3) {
        c = read();
        AddVal(u,v,c);
    }
    else {
        Query(u,v);
    }
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/ivorysi/p/9058136.html
今日推荐