题目传送门
题目大意
就是三个操作:
①将点A,B连一条无向边
②改变点A的权值
③问你从A到B的路径的权值和,其中边可以重复一个方向走,点可以重复走但权值只算一次。
题解
如果没有①②且给出的是树的话,就是一个求LCA。
如果没有①操作而是给出一个静态的树的话,那么就是一个树链剖分了。
如果保证是树(森林)的话,那就直接做一个LCT就行了。
关键就是它是一个图。
显然,如果A,B在一个环中的话,那么从A走到哪里都可以拿上B的权值(就可以通过环中一条路径走到B再通过另一条路径走回到A),于是通过一个点可以拿到其所在环的所有点的权值。于是我们发现题目其实就是让我们找环并缩环成点。然后就成了一片森林了,就可以用动态树来搞。类比Tarjan求有向图的强联通分量,这其实是在找无向图的边双连通分量。
我们如何找到环(或边双)并缩点呢?还要配合动态树的操作?这点我一开始觉得很难实现,其实用并查集是再合适不过的了。我们将每个点所在的环找一个“代表”,然后对于操作①就让“代表”连边。如果已经在一棵树上就代表需要缩环了,这时就将两个“代表”路径上的点的权值全部加在新的“代表”上,并将其他点删去。这样对于环上的所有点都可以看成“代表”这一个点。在
Access()
操作的时候从一条链通过path_parent边跳到上一条链上的点上时保证不要跳到已经删掉的点上,直接跳到那个点的“代表”上就行了。另外将更深的边砍掉也没有影响。
对于操作②的话对其“代表”还是照做,不过需要一点小改动而已。
操作③就直接做了。
由于每个点最多被合并和删掉一次,所以这个是 O(n) 的,加上LCT就是 O(mlogn+n)。
ps:此题万恶卡常。还有判断两个点是否在同一棵树时用 Find_Root() 被卡,但是由于无 Cut() 操作,直接多写个并查集就能过,搞的我超时了好多次。。。
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <cstring>
#define N 150005
using namespace std;
int n, m, cnt, haha[N], bcj[N], Fa[N];
inline int Read(){
int x = 0; char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + ch - '0'; ch = getchar();}
return x;
}
void Print(int x){
if(x > 9) Print(x / 10);
putchar(x % 10 + '0');
}
inline int find_r(int x){
return (bcj[x] == x) ? x : bcj[x] = find_r(bcj[x]);
}
struct Tnode{
Tnode *son[2], *fa;
int val, parent, sum, id;
bool rev;
int Get_d(){return fa->son[1] == this;}
inline void Connect(Tnode *now, int d){(son[d] = now)->fa = this;}
inline void Up(){
sum = val;
if(son[0]) sum += son[0]->sum;
if(son[1]) sum += son[1]->sum;
}
inline void Down(){
if(rev){
Tnode *now = son[0];
son[0] = son[1];
son[1] = now;
if(son[0]) son[0]->rev ^= 1;
if(son[1]) son[1]->rev ^= 1;
rev = false;
}
}
}tree[N], *Node[N];
inline Tnode *NewTnode(int id){
tree[cnt].son[0] = tree[cnt].son[1] = tree[cnt].fa = NULL;
tree[cnt].parent = tree[cnt].val = tree[cnt].sum = 0;
tree[cnt].id = id;
tree[cnt].rev = false;
return tree+cnt++;
}
inline void Zig(Tnode *now){
Tnode *last = now->fa;
int d = now->Get_d();
if(now->son[!d]) last->Connect(now->son[!d], d);
else last->son[d] = NULL;
if(last->fa) last->fa->Connect(now, last->Get_d());
else now->fa = NULL;
now->Connect(last, !d);
last->Up();
now->parent = last->parent;
last->parent = 0;
}
inline void Splay(Tnode *now){
Tnode *last;
while(now->fa){
last = now->fa;
if(last->fa) last->fa->Down();
last->Down(); now->Down();
if(last->fa) (now->Get_d() ^ last->Get_d()) ? Zig(now) : Zig(last);
Zig(now);
}
if(!now->fa) now->Down();
now->Up();
}
void Access(int x){
Splay(Node[x]);
if(Node[x]->son[1]){
Node[x]->son[1]->parent = x;
Node[x]->son[1]->fa = NULL;
Node[x]->son[1] = NULL;
Node[x]->Up();
}
int y = Node[x]->parent;
while(y = find_r(y)){
Splay(Node[y]);
if(Node[y]->son[1]){
Node[y]->son[1]->parent = y;
Node[y]->son[1]->fa = NULL;
Node[y]->son[1] = NULL;
Node[y]->Up();
}
Node[y]->Connect(Node[x], 1);
Node[y]->Up();
Node[x]->parent = 0;
x = y;
y = Node[x]->parent;
}
}
void Evert(int x){
Access(x); Splay(Node[x]); Node[x]->rev ^= 1;
}
void Link(int x, int y){
Evert(x); Node[x]->parent = y;
}
inline int Find_Root(int x){
return (Fa[x] == x) ? x : Fa[x] = Find_Root(Fa[x]);
}
void Gather(Tnode *&x, Tnode *y){
if(!x) return;
bcj[x->id] = y->id;
y->val += x->val;
Gather(x->son[0], y);
Gather(x->son[1], y);
x = NULL;
}
int main(){
freopen("bzoj2959.in", "r", stdin);
freopen("bzoj2959.out", "w", stdout);
n = Read(); m = Read();
for(int i = 1; i <= n; ++i) Node[i] = NewTnode(i);
for(int i = 1; i <= n; ++i){
haha[i] = Read();
Node[i]->sum = Node[i]->val = haha[i];
bcj[i] = Fa[i] = i;
}
int op, a, b;
for(int i = 1; i <= m; ++i){
op = Read(); a = Read(); b = Read();
if(op == 1){
a = find_r(a); b = find_r(b);
if(a == b) continue;
int x = Find_Root(a), y = Find_Root(b);
if(x ^ y) Link(a, b), Fa[x] = y;
else{
Evert(a); Access(b); Splay(Node[b]);
Gather(Node[b]->son[0], Node[b]);
Gather(Node[b]->son[1], Node[b]);
Node[b]->Up();
}
}
else if(op == 2){
int &temp = haha[a];
a = find_r(a);
Splay(Node[a]);
Node[a]->val += b - temp;
Node[a]->Up();
temp = b;
}
else{
a = find_r(a); b = find_r(b);
if(Find_Root(a) ^ Find_Root(b)) puts("-1");
else{
Evert(a); Access(b); Splay(Node[a]);
Print(Node[a]->sum);
putchar('\n');
}
}
}
return 0;
}
Only my railgun