算法學習:動態點/邊分治+[ZJOI2007]Hide 捉迷藏

動態點/邊分治算法學習

例題:[ZJOI2007]捉迷藏

luogu
bzoj
題目大意:給一顆樹,節點分黑白,開始全黑,給兩個操作,要麼把一個節點黑白變化,要麼詢問樹上最遠黑點距離

動態點分治

呼呼,終於寫(chao)完了這道動態點分治的題目。

首先不懂點分治的戳這裏(記得把題目也寫寫,寫完再來看這道)

對於這道題我們考慮不帶修改的情況,及直接詢問樹上最遠黑點的距離,顯然是一個裸的樹dp,只要用一個數組f記錄每個子樹中黑點到子樹根最遠距離,然後更新答案的時候用最大的加上次大的就可以很簡單地解決這道題。顯然,這個樹dp帶有很強的點分治的影子:考慮了每個子樹對根的影響。

此時我們考慮修改。如果暴力修改肯定是不行地。我們考慮修改的節點對答案的影響。發現這個節點僅僅會影響到其到跟路徑上的節點的答案,不會影響到其他節點的答案。因此,我們每次修改只需要修改這個節點到根節點的答案就可以了。

然而,如果樹是一條鏈,那麼我們的算法顯然會退化爲O(nmk)其中n是點數,m是修改次數,k是修改一個節點信息的複雜度。

因此我們希望有一種算法,使得每次修改的時間複雜度爲logn,也就是說,我們希望通過某種手段,使得每個節點修改後只需要修改logn個節點。像這樣,對於一顆無根樹進行修改和詢問,且可以將問題劃分到每棵子樹上的這類問題,我們有一種算法,叫做動態點分治。

其實,它和點分治一樣,只不過把樹dp的操作順序稍微修改了一下。

我們在原樹的基礎之上,建立出一顆新的“分治樹”,這棵分治樹的特點是它滿足點分治時的操作順序。也就是說,這顆分治樹上每個節點都是其子樹上所有節點在原樹上形成的連通塊的重心。這樣顯然滿足,這顆分治樹的樹高不會超過logn。那麼我們在進行樹dp或樹上操作的時候,建出分治樹並維護分治樹上每個節點的信息。修改的時候從待修改的節點往根向上更新,就可以保證修改的logn複雜度。

那麼,如何建立這顆分治樹呢,其實和點分治幾乎一模一樣,只不過在點分治的時候,加上一句prt[u] = pa,就可以把這可分治樹方便地建立出來,從而在上面進行操作。下面是代碼

void get_root(int u, int pa) {
	son[u] = 1; f[u] = 0;
	for(int i = pre[u]; i;i = e[i].next) 
		if(e[i].to != pa && !vis[e[i].to]) {
		get_root(e[i].to, u);
		son[u] += son[e[i].to];
		f[u] = max(f[u], son[e[i].to]);
	}
	f[u] = max(f[u], sums - son[u]);
	if(f[u] < f[root]) root = u;
}

void Div(int u, int pa) {
    prt[u] = pa; vis[u] = 1; int pre_sums = sums; 
    for(int i = pre[u]; i; i = e[i].next) 
    if(!vis[e[i].to]) {
        if(son[e[i].to] > son[u]) sums = pre_sums - son[u];
        else sums = son[e[i].to];
        root = 0;
        get_root(e[i].to, 0);
        Div(root, u);
    }
}

是不是不可思議地簡單!好了,接下來讓我們回到這一題。

記得不帶修改時的做法?用一個數組f記錄每個子樹中黑點到子樹根最遠距離,然後更新答案的時候用最大的加上次大的。顯然這裏麪包含了三層的最大:

  1.  子樹中黑點到子樹根最大
    
  2.  黑點到到每棵子樹最大值加上到父親距離的最大值
    
  3.  全局每個節點在(2)中的最大值加上次大值
    

對於這三個東西,我們維護三層的堆即可。爲了方便,我們把1稍微改一下,改成子樹中所有黑點到子樹根父親的最大值,這樣子省去了在更新時還要計算子樹和父親的距離(記得所有子樹和父親的距離要重新計算,因爲分治樹和原樹有一個映射的過程)

關於距離,當然是選擇rmq最快

關於堆,由於這個堆還要刪除,所以可以使用multiset(STL大法好)。然而hzwer採用了兩個priority_queue模擬出了multiset,真是吧STL使用得出神入化,orz,於是我get到了這個新技能。

最後說一下我的錯誤,當時沒有考慮到點分治面對的是一棵無根樹,於是隨便提了個跟並且限定了父親,在getroot的時候只帶了一個參數,判斷的時候強行用父子關係卡,結果當然是錯掉了qwq

代碼

全-代碼:好煩好煩啊

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
#define maxn 100005
#define maxm 200005
using namespace std;
int read()
{
	char ch = getchar(); int x = 0, f = 1;
	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;
}

struct Heap {
	priority_queue<int>A,B;
	void push(int x) {A.push(x);}
	void erase(int x) {B.push(x);}
	void pop() {
		while(B.size() && A.top() == B.top()) 
			A.pop(), B.pop();
	}
	int top() {
		pop();
		if(!A.size()) return 0;
		return A.top();
	}
	int size() {return A.size() - B.size();}
	int stop() {
		if(size() < 2) return 0;
		int x = top(); A.pop();
		int y = top(); push(x);
		return y;
	}
}A, B[maxn], C[maxn];

int pre[maxn], top;
struct edge
{
	int to, next;
	void add(int u, int v)
	{
		to = v; next = pre[u];
		pre[u] = top;
	}
}e[maxm];
int cnt, n, m, root, size, sums, tot, son[maxn], d[maxn], f[maxn], deep[maxn], pos[maxn];
int mn[maxm][18], bin[20], LOG[maxm], prt[maxn];
bool light[maxn], vis[maxn];

void adds(int u, int v)
{
	e[++top].add(u, v);
	e[++top].add(v, u);
}

void dfs(int u, int pa) {
	deep[u] = deep[pa] + 1;
	mn[++tot][0] = deep[u]; pos[u] = tot;
	for(int i = pre[u]; i; i = e[i].next) 
		if(e[i].to != pa)
		{
			dfs(e[i].to, u);
			mn[++tot][0] = deep[u];
		}
}

void RMQ_pre() {
	for(int j = 1; j <= LOG[tot]; ++j) 
		for(int i = 1;i + bin[j] - 1 <= tot; ++i)
			mn[i][j] = min(mn[i][j - 1], mn[i + bin[j - 1]][j - 1]); 
}

int rmq(int u, int v) {
	u = pos[u], v = pos[v];
	if(u > v) swap(u, v);
	int t = LOG[v - u + 1];
	return min(mn[u][t], mn[v - bin[t] + 1][t]);
}
int dis(int u, int v) {return deep[u] + deep[v] - (rmq(u, v) << 1);}

void get_root(int u, int pa) {
	son[u] = 1; f[u] = 0;
	for(int i = pre[u]; i;i = e[i].next) 
		if(e[i].to != pa && !vis[e[i].to]) {
		get_root(e[i].to, u);
		son[u] += son[e[i].to];
		f[u] = max(f[u], son[e[i].to]);
	}
	f[u] = max(f[u], sums - son[u]);
	if(f[u] < f[root]) root = u;
}

void Div(int u, int pa) {
    prt[u] = pa; vis[u] = 1; int pre_sums = sums; 
    for(int i = pre[u]; i; i = e[i].next) 
    if(!vis[e[i].to]) {
        if(son[e[i].to] > son[u]) sums = pre_sums - son[u];
        else sums = son[e[i].to];
        root = 0;
        get_root(e[i].to, 0);
        Div(root, u);
    }
}

void Turn_off(int u, int v) {
	if(u == v) {
		B[u].push(0);
		if(B[u].size() == 2) A.push(B[u].top());
	}
	if(!prt[u]) return;
	int pa = prt[u], D = dis(pa, v), tmp = C[u].top();
	C[u].push(D);
	if(D > tmp) {
		int mx = B[pa].top() + B[pa].stop(), size = B[pa].size();
		if(tmp) B[pa].erase(tmp);
		B[pa].push(D);
		int now = B[pa].top() + B[pa].stop();
		if(now > mx) {
			if(size >= 2) A.erase(mx);
			if(B[pa].size() >= 2) A.push(now);
		}
	}
	Turn_off(pa, v);
}


void Turn_on(int u, int v) {
	if(u == v) {
		if(B[u].size() == 2) A.erase(B[u].top());
		B[u].erase(0);
	}
	if(!prt[u]) return;
	int pa = prt[u], D = dis(pa, v), tmp = C[u].top();
	C[u].erase(D);
	if(D == tmp) {
		int mx = B[pa].top() + B[pa].stop(), size = B[pa].size();
		B[pa].erase(D);
		if(C[u].top()) B[pa].push(C[u].top());
		int now = B[pa].top() + B[pa].stop();
		if(now < mx) {
			if(size >= 2) A.erase(mx);
			if(B[pa].size() >= 2) A.push(now);
		}
	}
	Turn_on(pa, v);
}

void init() {
	bin[0] = 1; for(int i = 1;i < 20; ++i) bin[i] = bin[i - 1] << 1;
	LOG[0] = -1; for(int i = 1;i <= 200000; ++i) LOG[i] = LOG[i >> 1] + 1;
	cnt = n = read();
	for(int i = 1;i < n; ++i) adds(read(), read());
	dfs(1, 0); RMQ_pre();
	sums = n; root = 0; f[0] = 1000000000;
	get_root(1, 0); Div(root, 0);
	for(int i = 1;i <= n; ++i) Turn_off(i, i);
}

void solve() {
	m = read();
	while(m--) {
		char ch = getchar();
		while(ch != 'C' && ch != 'G') ch = getchar();
		if(ch == 'C') {
			int i = read();
			if(light[i]) Turn_off(i, i), ++cnt;
			else Turn_on(i, i), --cnt;
			light[i] ^= 1;
		}
		if(ch == 'G') {
			if(cnt <= 1) printf("%d\n", cnt - 1);
			else printf("%d\n", A.top());
		}
	}
}

int main()
{
	init();
	solve();
	return 0;
}

upd:2019.6.8

動態邊分治

這道題還可以用邊分治。
關於邊分治可以看這裏
動態邊分治其實換湯不換藥,還是照舊建出邊分樹即可。
當然如果是點修改的話,你還得把每個點對應的最後一層分治所屬的邊,然後記錄這個點是在左邊還是右邊。
對應這道題,在點分治中,因爲要考慮子樹的鏈並,所以要維護三個堆較爲繁瑣。
但是對應到邊分治,那麼你只需要兩邊維護堆,然後直接把兩邊最大值抓出來加起來就可以得到過這條邊的答案了。
全局答案在邊分樹上維護一下就好了。
是不是賊簡單。
關於修改的話,理論上可以把點掛在邊上,再記錄一下左右子樹,然後Lca求個距離。
不過這樣挺麻煩的,所以我就直接把一坨信息記錄下來(因爲樹是靜態的)就完事兒了。
當然啦,堆的刪除仍然是懶惰刪除。不過不需要寫可刪除堆的黑科技,直接標記一下黑白點,堆裏面順便維護一下對應點信息,如果這個點掛了把它彈出來即可。

代碼

比點分治簡單很多。

#include<bits/stdc++.h>
const int N = 1e5 + 10, inf = 1e9;
#define mp std::make_pair
#define fi first
#define se second
int ri() {
	char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f  = -1;
	for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
struct Edge {
	int pr[N << 1], nx[N << 2], to[N << 2], w[N << 2], tp;
	Edge() {tp = 1;}
	void add(int u, int v, int _w) {to[++tp] = v; nx[tp] = pr[u]; pr[u] = tp; w[tp] = _w;}
	void adds(int u, int v, int w = 0) {add(u, v, w); add(v, u, w);}
}R, T;
struct Data {
	int t, d; bool lr;
	Data(int _t = 0, bool _lr = 0, int _d = 0) : t(_t), lr(_lr), d(_d) {}
};
std::vector<Data>o[N];
std::priority_queue<std::pair<int, int> > q[N << 1][2];
bool col[N], del[N << 1]; int tot, light, n, mn, sums, G, rt, mx[N << 1], ch[N << 1][2], lst[N], sz[N << 1];
int Mx(std::priority_queue<std::pair<int, int> > &q) {
	for(;!q.empty() && col[q.top().se];)
		q.pop();
	return q.empty() ? -inf : q.top().fi;
}
void ins(int u, int v) {
	++tot; T.adds(tot, v, 1);
	T.adds(lst[u], tot); lst[u] = tot;
}
void Build(int u, int fa) {
	for(int i = R.pr[u], v; i;i = R.nx[i])
		if((v = R.to[i]) != fa)
			ins(u, v), Build(v, u);
}
void Rt(int u, int fa) {
	sz[u] = 1;
	for(int i = T.pr[u], v; i; i = T.nx[i])
		if(!del[i >> 1] && (v = T.to[i]) != fa) {
			Rt(v, u); sz[u] += sz[v];
			int tmp = std::max(sz[u], sums - sz[u]);
			if(tmp < mn)
				mn = tmp, G = i; 
		}
}
void Up(int p) {
	mx[p] = std::max(mx[ch[p][0]], mx[ch[p][1]]);
	mx[p] = std::max(mx[p], Mx(q[p][0]) + Mx(q[p][1]));
}
void Dfs(int u, int fa, int D, bool p, int nw) {
	if(u <= n) {
		q[nw][p].push(mp(D, u)); o[u].push_back(Data(nw, p, D));
	}
	for(int i = T.pr[u], v; i; i = T.nx[i])
		if(!del[i >> 1] && (v = T.to[i]) != fa)
			Dfs(v, u, D + T.w[i], p, nw);
}
int Div(int u, int pres) {
	if(pres == 1) return 0;
	sums = pres; mn = tot + 1; Rt(u, 0);
	int nw = G >> 1; del[nw] = true; 
	int x = T.to[G], y = T.to[G ^ 1], sy = pres - sz[x];
	Dfs(x, 0, 0, 0, nw); Dfs(y, 0, T.w[G], 1, nw);
	ch[nw][0] = Div(x, sz[x]); ch[nw][1] = Div(y, sy);
	return Up(nw), nw;
}
void setbk(int u) {
	col[u] = true; --light;
	for(int i = o[u].size() - 1; ~i; --i)
		Up(o[u][i].t);
}
void setwt(int u) {
	col[u] = false; ++light;
	for(int i = o[u].size() - 1; ~i; --i) {
		Data x = o[u][i];
		q[x.t][x.lr].push(mp(x.d, u));
		Up(x.t); 
	}
}
int main() {
	tot = n = ri();
	for(int i = 1;i < n; ++i) {
		int u = ri(), v = ri();
		R.adds(u, v);
	}
	for(int i = 1;i <= n; ++i) lst[i] = i;
	Build(1, 0);
	rt = Div(1, tot); light = n;
	for(int Q = ri(); Q--;) {
		char op = getchar();
		for(;op != 'G' && op != 'C';) op = getchar();
		if(op == 'G') 
			printf("%d\n", !light ? -1 : (light == 1 ? 0 : mx[rt]));
		else {
			int u = ri();
			col[u] ? setwt(u) : setbk(u);
		}
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章