BZOJ2631 tree(伍一鸣);以及标记下传的几点注意事项

最近又练了几道LCT的题,于是发现了这道好题……


Description

 一棵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的余数。

Input

  第一行两个整数n,q
接下来n-1行每行两个正整数u,v,描述这棵树
接下来q行,每行描述一个操作

Output

  对于每个/对应的答案输出一行

Sample Input

3 2
1 2
2 3
* 1 3 4
/ 1 1

Sample Output

4


HINT

数据规模和约定
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 
#include 
#include 
#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;
}


你可能感兴趣的:(OI)