BZOJ 2959: 长跑(LCT+并查集)

8 篇文章 0 订阅
4 篇文章 0 订阅

题目传送门


题目大意

就是三个操作:
①将点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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值