SP2939 QTREE5 - Query on a tree V(動態點分治 | LCT維護虛子樹)

在這裏插入圖片描述

洛谷鏈接


動態點分治做法:
基本上和 bzoj1095: [ZJOI2007]Hide 捉迷藏 類似的做法。

考慮每個點維護兩個 set,第一個 set 維護每個重心的子樹到它的父節點的最小距離,第二個 set 維護每個重心的每個子樹中白點到它的最小距離。查詢時先得到子樹中的答案,再暴力向上爬合併其他節點的答案。

LCT做法: 考慮 splay 每個節點維護子樹中當前節點距離最近的白點,查詢時將 vv 和根節點打通,並旋轉到根直接查詢。虛子樹的信息用一個 multiset 維護(multiset 維護每個虛子樹裏的白點到 v 的最小距離),容易發現在 splay 上難以通過 pushup 得到想要的信息,因爲一棵 splay 維護的是一條鏈,而一個節點的左右兒子並不是這條鏈上它的直接前驅後繼。

splay 的每一個子樹都代表這條鏈連續的一段,本質上合併子樹得到根節點的信息就是在合併兩段鏈,而我們需要的是這段鏈上深度最大的點的那個答案,設 lmn[x]lmn[x] 表示這段鏈上深度最低的點的答案,rmn[x]rmn[x] 表示這段鏈上深度最高的點的答案,虛子樹的信息仍用 multiset 進行維護。

虛子樹的信息只有在 access 操作時需要修改,pushup 操作就是兩段鏈的 lmn 和 rmn 進行合併(一般我寫成三段合併,根節點是隻有一個節點的鏈)
具體見代碼


動態點分治代碼:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
typedef long long ll;
#define pii pair<int,ll>
#define fir first
#define sec second
#define lowbit(i) (i & (-i))
struct Graph {
	int head[maxn], to[maxn << 1], cnt, nxt[maxn << 1], w[maxn << 1];
	void init() {
		memset(head,-1,sizeof head);
		cnt = 0;
	}
	void add(int u,int v,int c) {
		to[cnt] = v;
		nxt[cnt] = head[u];
		w[cnt] = c;
		head[u] = cnt++;
		
		to[cnt] = u;
		nxt[cnt] = head[v];
		w[cnt] = c;
		head[v] = cnt++;
	}
}G;
multiset<int> val[maxn],fval[maxn];
int st[maxn * 5][30],d[maxn],cnt,a[maxn];
int fir[maxn],vis[maxn],f[maxn],root,sz[maxn],siz,p[maxn];
int n,q,RT,A;
inline int read()
{
	int x=0,f=1;char ch;
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
void dfs1(int u,int fa) {			//預處理深度,st表,以及歐拉序 
	fir[u] = ++cnt; st[cnt][0] = u;
	for (int i = G.head[u]; i + 1; i = G.nxt[i]) {
		int v = G.to[i];
		if (v == fa) continue;
		d[v] = d[u] + G.w[i];
		dfs1(v,u);
		st[++cnt][0] = u;
	}
}
void dfs2(int u,int fa) {			//求一棵子樹的重心 
	sz[u] = 1; f[u] = 0;
	for (int i = G.head[u]; i + 1; i = G.nxt[i]) {
		int v = G.to[i];
		if (v == fa || vis[v]) continue;
		dfs2(v,u);
		sz[u] += sz[v];
		if (sz[v] > f[u]) f[u] = sz[v];
	}
	if (siz - sz[u] > f[u]) f[u] = siz - sz[u];
	if (!root || f[u] < f[root]) root = u;
}
void dfs3(int u,int fa) { 			//點分構建點分樹 
	vis[u] = 1;
	int all = siz;
	for (int i = G.head[u]; i + 1; i = G.nxt[i]) {
		int v = G.to[i];
		if (v == fa || vis[v]) continue;
		root = 0, siz = sz[v]; 
		if (siz > sz[u]) siz = all - sz[u];
		dfs2(v,u);
		p[root] = u;	//構建點分樹
		dfs3(root,u); 
	}
}
int cal(int u,int v) {
	return d[u] < d[v] ? u : v;
}
int getlca(int x,int y) {
	if (fir[x] > fir[y]) swap(x,y);
	int p = log2(fir[y] - fir[x] + 1);
	return cal(st[fir[x]][p],st[fir[y] - (1 << p) + 1][p]);
}
int getdis(int x,int y) {
	return d[x] + d[y] - 2 * d[getlca(x,y)];
}
void prework() {
	dfs1(1,0);
	for (int i = 1; i <= log2(cnt); i++)
		for (int j = 1; j + (1 << i) - 1 <= cnt; j++)
			st[j][i] = cal(st[j][i - 1],st[j + (1 << (i - 1))][i - 1]);
	root = 0, siz = n; dfs2(1,0);
	RT = root; dfs3(root,0);		//保留第一次的根節點,並構建點分樹 
}
void add(int x) {
	val[x].insert(0);
	for (int i = x; p[i]; i = p[i]) {
		if (fval[i].size()) {
			auto t = val[p[i]].lower_bound(*fval[i].begin());
			val[p[i]].erase(t);
		}
		fval[i].insert(getdis(x,p[i]));
		val[p[i]].insert(*fval[i].begin());
	}
}
void del(int x) {
	val[x].erase(0);
	for (int i = x; p[i]; i = p[i]) {
		if (fval[i].size()) {
			auto t = val[p[i]].lower_bound(*fval[i].begin());
			val[p[i]].erase(t);
		}
		auto t = fval[i].lower_bound(getdis(x,p[i]));
		fval[i].erase(t);
		if (fval[i].size())
			val[p[i]].insert(*fval[i].begin());
	}
}
int qry(int x) {
	int ans = 0x3f3f3f3f;
	if (val[x].size()) ans = min(ans,*val[x].begin());
	for (int i = x; p[i]; i = p[i]) {
		if (fval[i].size()) {
			auto t = val[p[i]].lower_bound(*fval[i].begin());
			val[p[i]].erase(t);
		}	
		if (val[p[i]].size())
			ans = min(ans,*val[p[i]].begin() + getdis(x,p[i]));
		if (fval[i].size())
			val[p[i]].insert(*fval[i].begin());
	}
	return ans;
}
int main() {
	n = read();
	G.init();
	for (int i = 1; i < n; i++) {
		int u,v; u = read(); v = read();
		G.add(u,v,1);
	}
	prework();
	for (int i = 1; i <= n; i++) {
		a[i] = 1;
	}
	q = read();
	int tot = 0;
	while (q--) {
		int x = read(), y = read();
		if (x == 1) {
			if (!tot) puts("-1");
			else printf("%d\n",qry(y));
		} else {
			a[y] ^= 1;
			if (a[y]) del(y), tot--;
			else add(y), tot++;
		}
	}
	return 0;
}

LCT代碼:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e6 + 10;
typedef long long ll;
#define pii pair<int,int>
#define fir first
#define sec second
int n,m,ans;
struct node {
	int u,v,a,b;
	bool operator < (const node &rhs) const {
		return a < rhs.a;
	}	
}E[maxn];
struct Graph {
	int head[maxn], to[maxn], nxt[maxn], cnt;
	void init() {
		cnt = 0;
		memset(head,-1,sizeof head);
	}
	void add(int u,int v) {
		to[cnt] = v;
		nxt[cnt] = head[u];
		head[u] = cnt++;
		
		to[cnt] = u;
		nxt[cnt] = head[v];
		head[v] = cnt++;  
	}
}G;
multiset<int> st;
inline int read(){
    int w=0,q=0; char c=getchar(); while((c<'0'||c>'9') && c!='-') c=getchar();
    if(c=='-') q=1,c=getchar(); while (c>='0'&&c<='9') w=w*10+c-'0',c=getchar(); return q?-w:w;
}
struct LCT {						//用splay維護原森林的連通,用到了splay的操作以及數組 
	#define ls ch[rt][0]
	#define rs ch[rt][1]
	#define inf 0x3f3f3f3f
	int ch[maxn][2];				//ch[u][0] 表示 左二子,ch[u][1] 表示右兒子
	int f[maxn];					//當前節點的父節點 
	int tag[maxn];					//翻轉標記,乘標記,加標記 
	int top,sta[maxn],sz[maxn],col[maxn]; 
	multiset<int> s[maxn];
	int lmn[maxn],rmn[maxn];		// splay 上每個節點對應子樹的最左端點的答案和最右端點的答案 
	inline bool get(int x) {
    	return ch[f[x]][1] == x;
	}
	void init() {
		for (int i = 1; i <= n; i++) {
			sz[i] = 1; col[i] = 0; lmn[i] = rmn[i] = inf;
		}
	}
	int fir(int x) {				// 取出第一個元素 
		return s[x].size() ? *s[x].begin() : inf;
	}
	inline void pushup(int rt) {
		if (rt) {
			sz[rt] = 1;
			lmn[rt] = rmn[rt] = (col[rt] ? 0 : fir(rt));
			if (ls) {
				lmn[rt] = min(lmn[ls],sz[ls] + lmn[rt]);
				rmn[rt] = min(rmn[rt],sz[rt] + rmn[ls]);
				sz[rt] += sz[ls];
			}
			if (rs) {
				lmn[rt] = min(lmn[rt],sz[rt] + lmn[rs]);
				rmn[rt] = min(rmn[rs],sz[rs] + rmn[rt]);
				sz[rt] += sz[rs];
			}
		}
	}
	inline bool isroot(int x) {
		return (ch[f[x]][0] != x) && (ch[f[x]][1] != x);
	}
 	inline void rotate(int x) {							//旋轉操作,根據 x 在 f[x] 的哪一側進行左旋和右旋 
	    int old = f[x], oldf = f[old];
		int whichx = get(x);
		if(!isroot(old)) ch[oldf][ch[oldf][1] == old] = x;		//如果 old 不是根節點,就要修改 oldf 的子節點信息
	    ch[old][whichx] = ch[x][whichx ^ 1];
	    ch[x][whichx ^ 1] = old;
	    f[ch[old][whichx]] = old;
	    f[old] = x; f[x] = oldf;
		pushup(old); pushup(x); 
	}
	inline void splay(int x) {								//將 x 旋到所在 splay 的根
    	for(int fa = f[x]; !isroot(x); rotate(x), fa = f[x]) {	//再把x翻上來
        	if(!isroot(fa))										//如果fa非根,且x 和 fa是同一側,那麼先翻轉fa,否則先翻轉x 
            	rotate((get(x) == get(fa)) ? fa : x);
        }
	}
	inline void access(int x) {					//access操作將x 到 根路徑上的邊修改爲重邊 
		int lst = 0;
		while(x > 0) {
			splay(x);
			if (lst) {
				auto tp = s[x].lower_bound(lmn[lst] + 1);
				s[x].erase(tp);
			}
			if (ch[x][1]) s[x].insert(lmn[ch[x][1]] + 1);
			ch[x][1] = lst;
			pushup(x);
			lst = x; x = f[x];
		}
	}
}tree;
void dfs(int u,int fa) {
	for (int i = G.head[u]; i + 1; i = G.nxt[i]) {
		int v = G.to[i]; 
		if (v == fa) continue;
		dfs(v,u);
		tree.f[v] = u; tree.s[u].insert(tree.lmn[v] + 1);
		tree.pushup(u);
	}
}
int id,x,y,u,v,op;
int main() {
	n = read();
	tree.init();
	G.init();
	for (int i = 1; i < n; i++) {
		u = read(); v = read();
		G.add(u,v); 
	}
	dfs(1,0);
	m = read();
	while (m--) {
		op = read(); v = read();
		if (op == 0) {
			tree.access(v); tree.splay(v);
			tree.col[v] ^= 1; tree.pushup(v); 
		} else {
			tree.access(v); tree.splay(v);
			printf("%d\n",tree.rmn[v] == inf ? -1 : tree.rmn[v]);
		}
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章