@uoj - 207@ 共价大爷游长沙


@description@

火车司机出秦川,跳蚤国王下江南,共价大爷游长沙。每个周末,勤劳的共价大爷都会开车游历长沙市。

长沙市的交通线路可以抽象成为一个 n 个点 n−1 条边的无向图,点编号为 1 到 n,任意两点间均存在恰好一条路径,显然两个点之间最多也只会有一条边相连。有一个包含一些点对 (x,y) 的可重集合S,共价大爷的旅行路线是这样确定的:每次他会选择 S 中的某一对点 (x,y),并从 x 出发沿着唯一路径到达 y。

小L是共价大爷的脑残粉,为了见到共价大爷的尊容,小L决定守在这张图的某条边上等待共价大爷的到来。为了保证一定能见到他,显然小L必须选择共价大爷一定会经过的边——也就是所有共价大爷可能选择的路径都经过的边。

现在小L想知道,如果他守在某一条边,是否一定能见到共价大爷。

然而长沙市总是不断的施工,也就是说,可能某个时刻某条边会断开,同时这个时刻一定也有某条新边会出现,且任意时刻图都满足任意两点间均存在恰好一条路径的条件。注意断开的边有可能和加入的新边连接着相同的两个端点。共价大爷的兴趣也会不断变化,所以S也会不断加入新点对或者删除原有的点对。当然,小L也有可能在任何时候向你提出守在某一条边是否一定能见到共价大爷的问题。你能回答小L的所有问题吗?

输入格式
输入的第一行包含一个整数 id,表示测试数据编号,如第一组数据的 id=1,样例数据的 id 可以忽略。hack数据中的 id 必须为 0 到 10 之间的整数。hack数据中id的值和数据类型没有任何关系。

输入的第二行包含两个整数 n,m,分别表示图中的点数,以及接下来会发生的事件数,事件的定义下文中会有描述。初始时 S 为空。

接下来 n−1 行,每行两个正整数 x,y,表示点 x 和点 y 之间有一条无向边。

接下来 m 行,每行描述一个事件,每行的第一个数 type 表示事件的类型。

若type=1,那么接下来有四个正整数x,y,u,v,表示先删除连接点x和点y的无向边,保证存在这样的无向边,然后加入一条连接点u和点v的无向边,保证操作后的图仍然满足题中所述条件。

若type=2,那么接下来有两个正整数 x,y,表示在 S 中加入点对 (x,y)。

若type=3,那么接下来有一个正整数 x,表示删除第 x 个加入 S 中的点对,即在第 x 个 type=2 的事件中加入 S 中的点对,保证这个点对存在且仍然在 S 中。

若type=4,那么接下来有两个正整数 x,y,表示小L询问守在连接点 x 和点 y 的边上是否一定能见到共价大爷,保证存在这样的无向边且此时 S 不为空。

输出格式
对于每个小L的询问,输出“YES”或者“NO”(均不含引号)表示小L一定能或者不一定能见到共价大爷。

样例一
input
0
5 7
1 2
1 3
2 4
1 5
2 1 5
1 1 5 2 5
4 2 5
2 1 4
4 2 5
3 1
4 2 4
output
YES
NO
YES

限制与约定
n <= 10^5, m <= 3*10^5。

@solution@

如果一条边被所有路径经过,这意味着这条边分割成的两棵子树分别包含所有路径的恰好一个端点。
相当于询问这条边分割成的两棵子树对应的路径端点集合,是否都等于总的路径端点集合。

我们可以使用集合哈希的技巧:给集合中每个元素一个极大的随机值,则集合的哈希值为元素随机值的异或和。
对于这个问题,给每条路径 (u, v) 一个极大的随机值 p,将点 u, v 的点权异或上 p。
然后查询这条边分割成的两棵子树内所有点权的异或和,这个值是否等于所有还存在的路径哈希值的异或和。

link 与 cut 显然就是 lct 了。
我们还要在 lct 中维护一棵子树的信息(子树内所有点的异或和)。可以通过维护 虚儿子的信息 与 虚儿子+实子树中的信息实现。
因为 access,link,cut 会影响一个结点的虚儿子,在这些操作中维护一下即可。

@accepted code@

#include<vector>
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
const int MAXN = 100000;
const int MAXM = 300000;
struct link_cut_tree{
	struct node{
		bool rev;
		ull key, sum, vsum;
		node *fa, *ch[2];
		bool dir() {return fa->ch[1] == this;}
	}pl[MAXN + 5], *ncnt, *NIL;
	link_cut_tree() {
		ncnt = NIL = &pl[0];
		NIL->ch[0] = NIL->ch[1] = NIL->fa = NIL;
	}
	node *newnode() {
		node *p = (++ncnt);
		p->ch[0] = p->ch[1] = p->fa = NIL;
		return p;
	}
	void maintain(node *x) {
		if( x != NIL ) {
			swap(x->ch[0], x->ch[1]);
			x->rev ^= 1;
		}
	}
	void pushdown(node *x) {
		if( x->rev ) {
			maintain(x->ch[0]), maintain(x->ch[1]);
			x->rev = 0;
		}
	}
	void pushup(node *x) {
		x->sum = x->ch[0]->sum ^ x->ch[1]->sum ^ x->vsum ^ x->key;
	}
	bool is_root(node *x) {
		return x->fa->ch[0] != x && x->fa->ch[1] != x;
	}
	void set_child(node *x, node *y, int d) {
		if( x != NIL ) x->ch[d] = y;
		if( y != NIL ) y->fa = x;
	}
	void rotate(node *x) {
		node *y = x->fa; int d = x->dir();
		pushdown(y), pushdown(x);
		if( is_root(y) ) x->fa = y->fa;
		else set_child(y->fa, x, y->dir());
		set_child(y, x->ch[!d], d);
		set_child(x, y, !d);
		pushup(y);
	}
	void splay(node *x) {
		pushdown(x);
		while( !is_root(x) ) {
			node *y = x->fa;
			if( is_root(y) ) rotate(x);
			else {
				if( y->dir() == x->dir() )
					rotate(y);
				else rotate(x);
				rotate(x);
			}
		}
		pushup(x);
	}
	void access(node *x) {
		node *y = NIL;
		while( x != NIL ) {
			splay(x);
			x->vsum ^= y->sum ^ x->ch[1]->sum;
			x->ch[1] = y;
			pushup(x);
			y = x, x = x->fa;
		}
	}
	void make_root(node *x) {
		access(x), splay(x), maintain(x);
	}
	void link(node *x, node *y) {
		make_root(x), make_root(y);
		x->fa = y, y->vsum ^= x->sum, pushup(y);
	}
	void cut(node *x, node *y) {
		make_root(x), access(x), splay(y);
		y->fa = NIL, x->vsum ^= y->sum, pushup(x);
	}
	void modify(node *x, ull k) {
		make_root(x), access(x);
		x->key ^= k, pushup(x);
	}
	ull query(node *x, node *y) {
		make_root(x), access(y), splay(x); 
		return y->sum;
	}
}T;                      
link_cut_tree::node *nd[MAXN + 5];
ull get_rand() {
	ull p1 = rand(), p2 = rand(), p3 = rand(), p4 = rand();
	return (((((p1 << 16) | p2) << 16) | p3) << 16) | p4;
}
struct node{
	int x, y; ull k;
	node(int _x=0, int _y=0, ull _k=0):x(_x), y(_y), k(_k) {}
};
vector<node>vec;
ull nw;
int main() {
	int id; scanf("%d", &id);
	int n, m; scanf("%d%d", &n, &m);
	srand(20041112^n^id);
	for(int i=1;i<=n;i++)
		nd[i] = T.newnode();
	for(int i=1;i<n;i++) {
		int x, y; scanf("%d%d", &x, &y);
		T.link(nd[x], nd[y]);
	}
	for(int i=1;i<=m;i++) {
		int type; scanf("%d", &type);
//		printf("%d %d\n", i, type);
		if( type == 1 ) {
			int x, y, u, v; scanf("%d%d%d%d", &x, &y, &u, &v);
			T.cut(nd[x], nd[y]), T.link(nd[u], nd[v]);
		}
		else if( type == 2 ) {
			int x, y; scanf("%d%d", &x, &y);
			ull p = get_rand(); nw ^= p;
			vec.push_back(node(x, y, p));
			T.modify(nd[x], p), T.modify(nd[y], p);
		}
		else if( type == 3 ) {
			int x; scanf("%d", &x), x--;
			node p = vec[x]; nw ^= p.k;
			T.modify(nd[p.x], p.k), T.modify(nd[p.y], p.k);
		}
		else {
			int x, y; scanf("%d%d", &x, &y);
			puts(T.query(nd[x], nd[y]) == nw ? "YES" : "NO");
		}
	}
}

@details@

原本应该是给点权异或上一个数,结果我写成了给点权赋值。。。

集合哈希的技巧的确挺少见的,但非常有用,碰撞率比字符串哈希的碰撞率还低。

posted @ 2019-10-11 19:10  Tiw_Air_OAO  阅读(225)  评论(0编辑  收藏  举报