P1501 [国家集训队]Tree II
知识点:LCT
原题面:Luogu。
LCT 相关内容可以阅读:「笔记」Link Cut Tree。
简述
给定一 \(n\) 个节点的树,初始点权为 \(1\)。给定 \(m\) 次操作:
- 路径加。
- 路径乘。
- 删去一条边,再加入一条边,保证操作完之后仍然是一棵树。
- 查询路径和,答案对 \(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;
}
作者@Luckyblock,转载请声明出处。