P1501 [国家集训队]Tree II

知识点:LCT

原题面:Luogu

LCT 相关内容可以阅读:「笔记」Link Cut Tree

简述

给定一 \(n\) 个节点的树,初始点权为 \(1\)。给定 \(m\) 次操作:

  1. 路径加。
  2. 路径乘。
  3. 删去一条边,再加入一条边,保证操作完之后仍然是一棵树。
  4. 查询路径和,答案对 \(51061\) 取模。

\(1\le n,m\le 10^5\)
2S,512MB。

分析

LCT 板题,详见注释。注意 LL。

代码

//知识点:LCT
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e5 + 10;
const LL mod = 51061;
//=============================================================
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
namespace LCT {
  #define f fa[now_]
  #define ls son[now_][0]
  #define rs son[now_][1]
  const int kMaxNode = kN;
  int fa[kMaxNode], son[kMaxNode][2], val[kMaxNode]; //Splay 的结构信息
  LL siz[kMaxNode], sum[kMaxNode], tagplus[kMaxNode], tagprod[kMaxNode]; //子树大小,子树和,两种标记
  bool tagrev[kMaxNode]; //Splay 的子树反转标记
  void Pushup(int now_) { //维护子树和 和 子树大小
    sum[now_] = (sum[ls] + sum[rs] + val[now_]) % mod;
    siz[now_] = siz[ls] + siz[rs] + 1;
  }
  void PushReverse(int now_) { //子树反转标记,使得 Splay 节点的中序遍历反向。若原 splay 表示一条自顶向下的链,反转相当于将 splay 表示的链的边反向,父子关系互换。
    if (!now_) return;
    std::swap(ls, rs);
    tagrev[now_] ^= 1;
  }
  void PushPlus(int now_, LL val_) {
    if (!now_) return;
    val[now_] = (val[now_] + val_) % mod;
    sum[now_] = (sum[now_] + siz[now_] * val_ % mod) % mod;
    tagplus[now_] = (tagplus[now_] + val_) % mod;
  }
  void PushProd(int now_, LL val_) {
    if (!now_) return;
    val[now_] = val[now_] * val_ % mod;
    sum[now_] = sum[now_] * val_ % mod;
    tagplus[now_] = tagplus[now_] * val_ % mod;
    tagprod[now_] = tagprod[now_] * val_ % mod;
  }
  void Pushdown(int now_) { //注意下放顺序
    LL plus = tagplus[now_], prod = tagprod[now_], rev = tagrev[now_];
    if (prod != 1) PushProd(ls, prod), PushProd(rs, prod);
    if (plus) PushPlus(ls, plus), PushPlus(rs, plus);
    if (rev) PushReverse(ls), PushReverse(rs);
    tagprod[now_] = 1, tagplus[now_] = 0, tagrev[now_] = 0;
  }
  bool IsRoot(int now_) { //判断 now_ 是否为当前 Splay 的根
    return son[f][0] != now_ && son[f][1] != now_;
  }
  bool WhichSon(int now_) {
    return son[f][1] == now_;
  }
  void Rotate(int now_) {
    int fa_ = f, w = WhichSon(now_);
    if (!IsRoot(f)) son[fa[f]][WhichSon(f)] = now_;
    f = fa[f];

    son[fa_][w] = son[now_][w ^ 1];
    fa[son[fa_][w]] = fa_;

    son[now_][w ^ 1] = fa_;
    fa[fa_] = now_;
    Pushup(fa_), Pushup(now_); 
  }
  void Update(int now_) { //将 Splay 路径上的所有标记下放
    if (!IsRoot(now_)) Update(f);
    Pushdown(now_);
  }
  void Splay(int now_) {
    Update(now_);
    for (; !IsRoot(now_); Rotate(now_)) {
      if (!IsRoot(f)) Rotate(WhichSon(f) == WhichSon(now_) ? f : now_);
    }
  }
  void Access(int now_) { //使得树中由根->now 的链成为实链,构造出由它们组成的 Splay,满足 Splay 的中序遍历深度递减的性质。
    //自下向上构建,舍弃父亲的原有右儿子,换成 -> last 的链。
    for (int last_ = 0; now_; last_ = now_, now_ = f) {
      Splay(now_), rs = last_;
      Pushup(now_);
    }
  }
  void MakeRoot(int now_) { //使 now 成为原树的根
    Access(now_); //先使得树中由根->now 的链成为实链,构造出由它们组成的 Splay。
    Splay(now_); //使 now 成为 splay 的根节点
    PushReverse(now_); //将根->now 的链反转,使 now 成为原树的根。原理参考 PushReverse 函数的注释。
  }
  int Find(int now_) { //找到 now_ 所在原树的根
    Access(now_); 
    Splay(now_);
    while (ls) Pushdown(now_), now_ = ls; //使得树中由根->now 的链成为实链,构造出由它们组成的 Splay,再找到 Splay 中序遍历的第一个元素,即为原树的根。
    
    Splay(now_); //为了下一步操作,把根再转回去
    return now_; 
  }
  void Split(int x_, int y_) { //构造由路径 x->y 组成的 Splay
    MakeRoot(x_); //使 x 成为根,构造出根 -> y 的 Splay 即得,Splay 根的子树信息即为路径信息。
    Access(y_);
    Splay(y_);
  }
  void Link(int x_, int y_) { //加边 (x,y)
    MakeRoot(x_); //使 x 成为根,再给根一个父亲
    if (Find(y_) != x_) fa[x_] = y_;
  }
  void Cut(int x_, int y_) { //删边 (x,y)
    MakeRoot(x_); //使 x 成为根
    //Find(y_) != x_ 保证 y 与 x 连通
    //在 Find 函数中 Access(y) 之后,y 所在的 splay 由 x->y 的链构成。又 x 是 splay 的根,y 的中序遍历在 x 后,则若边 (x,y) 存在,则 y 的位置只有一种可能:  
    //y 是 x 的右儿子,且 y 没有左儿子。从而保证 y 在中序遍历中是 x 的后一个。
    if (Find(y_) != x_ || fa[y_] != x_ || son[y_][0]) return ;
    fa[y_] = son[x_][1] = 0; //断绝父子关系
    Pushup(x_);
  }
  void Modify(int x_, int y_, int val_, int type) { //路径修改
    Split(x_, y_); //给路径构成的 Splay 打标记
    if (!type) PushPlus(y_, val_);
    else PushProd(y_, val_);
  }
  LL Query(int x_, int y_) { //路径查询
    Split(x_, y_); //返回路径构成的 Splay 的信息
    return sum[y_];
  }
}
//=============================================================
int main() { 
  int n = read(), m = read();
  for (int i = 1; i <= n; ++ i) {
    LCT::val[i] = LCT::tagprod[i] = LCT::siz[i] = 1;
  }
  for (int i = 1; i < n; ++ i) {
    int u_ = read(), v_ = read();
    LCT::Link(u_, v_);
  }
  for(int i = 1; i <= m; i ++) {
	  char opt[2]; scanf("%s", opt);
	  int u1 = read(), v1 = read();
	  if(opt[0] == '+') LCT::Modify(u1, v1, read(), 0);
	  if(opt[0] == '*') LCT::Modify(u1, v1, read(), 1);
	  if(opt[0] == '/') printf("%lld\n", LCT::Query(u1, v1));
    if(opt[0] == '-') {
      int u2 = read(), v2 = read(); 
      LCT::Cut(u1, v1), LCT::Link(u2, v2);
    }
	}
  return 0; 
}
posted @ 2021-01-25 15:57  Luckyblock  阅读(59)  评论(1编辑  收藏  举报