一棵n个点的树,每个点的初始权值为1。对于这棵树有q个操作,每个操作为以下四种操作之一:
+ u v c:将u到v的路径上的点的权值都加上自然数c;
- u1 v1 u2 v2:将树中原有的边(u1,v1)删除,加入一条新边(u2,v2),保证操作完之后仍然是一棵树;
* u v c:将u到v的路径上的点的权值都乘上自然数c;
/ u v:询问u到v的路径上的点的权值和,求出答案对于51061的余数。
最近又练了几道LCT的题,于是发现了这道好题……
一棵n个点的树,每个点的初始权值为1。对于这棵树有q个操作,每个操作为以下四种操作之一:
+ u v c:将u到v的路径上的点的权值都加上自然数c;
- u1 v1 u2 v2:将树中原有的边(u1,v1)删除,加入一条新边(u2,v2),保证操作完之后仍然是一棵树;
* u v c:将u到v的路径上的点的权值都乘上自然数c;
/ u v:询问u到v的路径上的点的权值和,求出答案对于51061的余数。
数据规模和约定
10%的数据保证,1<=n,q<=2000
另外15%的数据保证,1<=n,q<=5*10^4,没有-操作,并且初始树为一条链
另外35%的数据保证,1<=n,q<=5*10^4,没有-操作
100%的数据保证,1<=n,q<=10^5,0<=c<=10^4
这题相当坑爹……明明LCT的代码和一道已经AC了的题的代码是一样的,对拍还是过不了,还一度怀疑自己的LCT模板是错的……最后上网找别人的题解(百度只找到两篇),发现原来:取模的数51061^2会爆int……改成long long就能AC了……
吐槽先到这里
这题一共三类操作,+和*操作都是修改链上点权,-操作是改变树的形态,/操作是查询。具体细节可以看之前的《LCT小结》文章,这里重点讲一下标记的问题。
个人把标记分为了两种:覆盖类标记和非覆盖类标记。所谓覆盖类标记就是,这个标记对区间的影响是和这个区间的值以及已有的标记无关的,比如区间赋值或者区间翻转。其他的标记就是非覆盖类标记,比如区间加减乘除、区间取反(对于01串)之类的。
对于覆盖类标记不用想太多,如果是像区间赋值这样会“覆盖”已有的非覆盖类标记的标记,那就清除已有标记,然后直接打上新的标记;如果是区间翻转这种和其他标记独立开来的标记,就单独处理。处理这类标记时不需要先pushdown。
对于非覆盖类标记就要仔细思考了。因为标记之间有可能互相影响,所以处理标记的顺序是很重要的。就拿这道题来说,有两类非覆盖类标记:加法标记和乘法标记。应该先下传乘法标记,再下传加法标记。同时下传乘法标记时,要给儿子的加法增量也乘上乘法标记,写成公式就是(x + delta) * c = x * c + delta * c。有同样标记的线段树题有一道BZOJ1798。Splay和线段树中的标记下传类似(可以说完全相同)。
还有就是标记下传(pushdown)与信息更新(update)的时机。由于下传的时候会直接修改子节点的value和sum(如果有)等信息,也就是说,打了标记的节点本身的信息已经是最新了的,所以不要update,update反而错了。个人总结的Splay需要pushdown的地方分别是zig和zag过程中对x进行下传、splay过程中每次旋转前对x的祖父节点(如果有)和父节点依次下传,最后在splay过程结束之前先下传再更新。详见代码。
这题还有另外一个需要注意的地方,也是一个很强的常数优化,就是由于题目保证了所有数据肺腑,所以可以用unsigned int而非long long来存储。实测unsigned int比long long要快将近一倍。
代码:(目前在BZOJ和清橙上都是最快的……大概可以认为这个模板常数还是比较优的吧?)
//Tsinsen A1303; tree; Link-Cut Tree #include <cstdio> #include <cstdlib> #include <algorithm> #define N 100000 typedef unsigned int uint; const uint mod = 51061; struct edge { uint next, node; }e[N << 1 | 1]; uint head[N + 1], tot = 0; uint n, q, x, y, w; bool v[N + 1]; class Node { public: Node *l, *r, *f; uint v, s, sum, add, mul; bool rev; Node() {} Node(uint _v) : s(1), add(0), mul(1), rev(false) { l = r = f = NULL, sum = v = _v; } }*p[N + 1]; char cmd[10]; inline void addedge(uint a, uint b) { e[++tot].next = head[a]; head[a] = tot, e[tot].node = b; } inline uint add(uint a, uint b) { return a + b >= mod ? a + b - mod : a + b; } inline void mul(uint &a, uint b) { a = (a * b) % mod; } inline bool isRoot(Node *x) { return !(x->f) || (x->f->l != x && x->f->r != x); } inline void update(Node *x) { x->sum = add(x->v, add((x->l ? x->l->sum : 0), (x->r ? x->r->sum : 0))); x->s = (x->l ? x->l->s : 0) + (x->r ? x->r->s : 0) + 1; } inline void modifyTree(Node *x, uint a, uint m) { if (m != 1) { mul(x->mul, m); mul(x->sum, m); mul(x->v, m); mul(x->add, m); } if (a) { x->add = add(x->add, a); x->sum = add(x->sum, (x->s * a) % mod); x->v = add(x->v, a); } } inline void push(Node *x) { if (x->rev) { x->rev = false; if (x->l) x->l->rev ^= 1; if (x->r) x->r->rev ^= 1; std::swap(x->l, x->r); } if (!(x->add) && x->mul == 1) return ; if (x->l) modifyTree(x->l, x->add, x->mul); if (x->r) modifyTree(x->r, x->add, x->mul); x->add = 0, x->mul = 1; } inline void zig(Node *x) { Node *y = x->f; Node *z = y->f; push(x); if (z && z->l == y) z->l = x; else if (z && z->r == y) z->r = x; if (x->r) x->r->f = y; y->l = x->r; x->r = y, y->f = x, x->f = z; update(y); } inline void zag(Node *x) { Node *y = x->f; Node *z = y->f; push(x); if (z && z->l == y) z->l = x; else if (z && z->r == y) z->r = x; if (x->l) x->l->f = y; y->r = x->l; x->l = y, y->f = x, x->f = z; update(y); } inline void splay(Node *x) { Node *y, *z; while (!isRoot(x)) { y = x->f; z = y->f; if (z) push(z); push(y); if (isRoot(y)) if (y->l == x) zig(x); else zag(x); else if (z->l == y) if (y->l == x) zig(y), zig(x); else zag(x), zig(x); else if (y->l == x) zig(x), zag(x); else zag(y), zag(x); } push(x); update(x); } inline void expose(Node *x) { for (Node *y = NULL; x; x = x->f) { splay(x); x->r = y; update(y = x); } } inline void makeRoot(Node *x) { expose(x); splay(x); x->rev ^= 1; } inline void link(Node *x, Node *y) { makeRoot(x); x->f = y; } inline void cut(Node *x, Node *y) { expose(y); splay(x); if (x->f == y) x->f = NULL; else { expose(x); splay(y); y->f = NULL; } } void dfs(uint x) { v[x] = true; p[x] = new Node(1); for (uint i = head[x]; i; i = e[i].next) { if (v[e[i].node]) continue; dfs(e[i].node); p[e[i].node]->f = p[x]; } } inline void modify(uint _x, uint _y, uint a, uint m) { Node *x = p[_x], *y = p[_y]; expose(y); for (y = NULL; x; x = x->f) { splay(x); if (!(x->f)) { if (y) modifyTree(y, a, m); if (x->r) modifyTree(x->r, a, m); x->v = add(x->v, a); mul(x->v, m); } x->r = y; update(y = x); } } inline uint query(uint _x, uint _y) { uint ret = 0; Node *x = p[_x], *y = p[_y]; expose(y); for (y = NULL; x; x = x->f) { splay(x); if (!(x->f)) { ret = x->v; if (y) ret = add(ret, y->sum); if (x->r) ret = add(ret, x->r->sum); break; } x->r = y; update(y = x); } return ret; } int main() { #ifndef ONLINE_JUDGE freopen("input.txt", "r", stdin); freopen("output.txt", "w", stdout); #endif scanf("%d%d", &n, &q); for (uint i = 1; i < n; ++i) { scanf("%d%d", &x, &y); addedge(x, y), addedge(y, x); } dfs(1); while (q--) { scanf("%s%d%d", cmd, &x, &y); if (cmd[0] == '+' || cmd[0] == '*') //Add x y w / Mul x y w { scanf("%d", &w); if (cmd[0] == '+') modify(x, y, w % mod, 1); else modify(x, y, 0, w % mod); } else if (cmd[0] == '-') //Cut x y Link a b { cut(p[x], p[y]); scanf("%d%d", &x, &y); link(p[x], p[y]); } else //Sum x y printf("%d\n", query(x, y)); } return 0; }