P3348 [ZJOI2016]大森林

link

有性质:

  • 每个0操作范围如果为\(1~n\),只要1操作正确(不会把生长节点移到不存在的节点),不会影响询问的答案

  • 性质2,对于每棵树,先修改完,再询问,答案也不会变

所以我们离线处理,从前往后扫树,只维护一棵\(lct\),只需修改第\(i\)棵树与\(i-1\)棵的不同。

对于生长节点的修改,建虚点,修改生长节点\(x\)次,就建\(x+1\)个虚点,将虚点连起来,最上面连着\(1\)号根节点(实点),\(i\)虚点侧面连上第i次成为生长节点的点在下一次修改生长节点前,加进来的儿子(实点)。

对于生长节点第\(id\)次被改,\([l, r]\)树内生长节点改为了\(x\),首先根据\([l, r]\)\(x\)存在的区间,确定出生长节点需要改为\(x\)的区间,离线处理时, 扫到第\(l\)棵树,其\(id\)号虚点\(cut\)掉父边,\(link\)\(x\), 扫到第\(r + 1\)棵树,其\(id\)号虚点\(cut\)掉父边,\(link\)\(id-1\)号虚点。

每棵树先修改完再询问。

对于询问,注意\(dis[x,y]\)不能换根然后\(split\),要用差分找\(dis\)\(dep[x]+dep[y]-2*dep[lca(x,y)]\),\(lca\)\(access\)时候稍微维护一下就好,至于dep,只需记录该节点到根节点路径上实点的个数。

为什么不能换根,因为这是有根树,我们要维护原树中确定的父子关系,才能正确的\(cut\)掉父边

Code
#include <bits/stdc++.h>
#define rint register int
#define F(x) (t[x].fa)
#define L(x) (t[x].ch[0])
#define R(x) (t[x].ch[1])
#define get(x) (x == t[F(x)].ch[1])
#define not_root(x) (x == t[F(x)].ch[0] || x == t[F(x)].ch[1])
#define up(x) ({ t[x].sum = t[L(x)].sum + t[R(x)].sum + t[x].val; })

const int maxn = 1e5, maxm = 2e5;

int n, m, xc = 1, nc = 1, ac;
int fwl[maxm], fwr[maxm];// 每个点的存在范围
int ans[maxm];
struct Node{
	int val, sum, fa, ch[2];
}t[maxm];
struct Sim{
	int pos, opt, x, y, z;
	bool operator < (const Sim &B)const{
		if(pos != B.pos) return pos < B.pos;
		return opt < B.opt;
	}
}a[maxm * 2];
struct Tmp{
	int opt, l, r, x;
}tmp[maxm];

int read(rint x = 0, register bool f = 0, register char ch = getchar()){
	for(;!isdigit(ch);ch = getchar()) f |= ch == '-';
	for(; isdigit(ch);ch = getchar()) x = (x << 3) + (x << 1) + (ch & 15);
	return f ? -x : x ;
}

void add(rint x, rint f, rint d){
	if(x) F(x) = f;
	if(f) t[f].ch[d] = x;
}

void rotate(rint x){
	rint y = F(x), z = F(y), gx = get(x), gy = get(y);
	if(not_root(y)) add(x, z, gy);
	else F(x) = z;
	add(t[x].ch[gx ^ 1], y, gx), add(y, x, gx ^ 1);
	up(y);
}

void splay(rint x){
	while(not_root(x)){
		rint y = F(x);
		if(not_root(y)) get(x) != get(y) ? rotate(x) : rotate(y);
		rotate(x);
	}
	up(x);
}

int access(rint x){
	rint y = 0;
	for(;x;y = x, x = F(x)) splay(x), R(x) = y, up(x);
	return y;//返回lca
}

void link(rint x, rint y){
	splay(x), F(x) = y; // 不需要access,我们没有换根,所以这个x是其子树的父亲
}
void cut(rint x){
	access(x), splay(x), L(x) = F(L(x)) = 0, up(x);//这里就需要access了,因为要让他父亲找到他
}

int cal(rint x, rint y){
	rint ans = 0, lca;
	access(x), splay(x), ans = t[x].sum;
	lca = access(y), splay(y), ans += t[y].sum;
	access(lca), splay(lca), ans -= t[lca].sum * 2;
	return ans;
}

int main(){
#ifdef lzx
	freopen("1.in","r",stdin);
#endif
	n = read(), m = read();
	fwl[1] = 1, fwr[1] = n; // 注意1的初始化
	for(rint i = 1;i <= m; ++i){// 读取修改和询问,处理出虚点和实点的个数,实点编号1 ~ nc,虚点编号nc + 1 ~ xc
		tmp[i].opt = read(), tmp[i].l = read(), tmp[i].r = read();
		if(tmp[i].opt != 0) tmp[i].x = read();
		switch(tmp[i].opt){
			case 0: fwl[++nc] = tmp[i].l, fwr[nc] = tmp[i].r; break; // nc实点个数
			case 1: ++xc; break; // 虚点个数
		}
	}
	for(rint i = 1;i <= nc; ++i) t[i].val = 1;
	link(nc + 1, 1); // 注意link传参的顺序不要变,父节点在后
	for(rint i = 1, nown = 1, nowx = 1, l, r;i <= m; ++i){//先把初始的树建出来,
		switch(tmp[i].opt){
			case 0: link(++nown, nowx + nc); break;
			case 1:
				link(nowx + 1 + nc, nowx + nc), ++nowx;
				l = std:: max(tmp[i].l, fwl[tmp[i].x]);
				r = std:: min(tmp[i].r, fwr[tmp[i].x]);
				if(l <= r){
					a[++ac] = (Sim){l, 1, nowx + nc, tmp[i].x};
					a[++ac] = (Sim){r + 1, 1, nowx + nc, nowx - 1 + nc};
				}
				break;
			case 2: a[++ac] = (Sim){tmp[i].l, 2, tmp[i].r, tmp[i].x, i}; break;
		}
	}
	std:: sort(a + 1, a + 1 + ac);
	for(rint i = 1, j = 1;i <= n; ++i){//从前往后,对于一棵树,先处理link和cut,再处理询问
		for(;a[j].pos == i; ++j){
			if(a[j].opt == 1) cut(a[j].x), link(a[j].x, a[j].y);
			if(a[j].opt == 2) ans[a[j].z] = cal(a[j].x, a[j].y);
		}
	}
	for(rint i = 1;i <= m; ++i) if(tmp[i].opt == 2) printf("%d\n", ans[i]);
	return 0;
}
posted @ 2020-12-23 10:31  liuzhaoxu  阅读(73)  评论(0编辑  收藏  举报