虛樹詳解

虛樹大意:通過建一棵只包含詢問點和不超過詢問點數減一個lca的樹來減少點的個數,降低時間複雜度

這東西不難,常用於輔助樹形dp,難點在於dp…
使用該算法的標誌爲不確定組的詢問次數與給出的詢問點數和 例如:
每次詢問w個數,i=1qwi=300000\sum_{i=1}^qw_i=300000\quad


算法:

構建過程十分簡單,放心食用
舉個例子:P2495 [SDOI2011]消耗戰
就是找一些邊割掉使得根與所有選定點不連通,使割掉的邊權和最小
首先給出一棵樹:(原諒我醜陋的圖)

在這裏插入圖片描述
紅色的點是我們要詢問的,明顯我們不可能直接在原樹上搞O(n)dp,我要是全部點都選不就gg了。。
所以我們考慮省掉一些點來優化總點數。

很容易觀察到,3和7號節點並沒有對答案造成貢獻,如果只是找最小邊權只需要用一個前綴記錄就可以,那麼如何優化呢?

明顯我們需要保證虛樹仍保持節點深度關係的同時保證不加入無用點。對選定點按照dfn排序再依次插入,用棧記錄當前的一條鏈,那麼可見我們新加入一個點x會有以下兩種情況:

  1. 棧頂是x的祖先,直接加進去
  2. 棧頂不是x的祖先,求棧頂和x的lca,不停出棧,並連一條stk[top-1]->stk[top]的邊(構建當前鏈),直到找到棧內最後一個點的dfn>=lca的dfn,即棧頂上一個點的dfn<lca的dfn。如果棧頂的點不是lca,則連一條lca到棧頂的邊,再把棧頂改成lca,加入x,保持深度遞增。

其實我們的虛樹的深度就是當前根到x路徑上的選定點個數,加入lca是因爲要使得同一深度的點保持這種關係。舉個栗子,x和y的深度都是4,你連x->y或者y->x都會改變原樹形態,此時就要lca->x,lca->y。
我們又按照了dfn排序,此時選定點有序,只需要處理相鄰兩點之間的位置問題即可。

注意一下,第一個點永遠置爲1,方便搜索;最後將棧內的全部出棧加邊。

可以通過上面的圖理解:

首先點排序後長這樣:4 10 11 9 5

  1. stk:1 (初始)
  2. stk:1 4 此時加入了4,棧頂1是4的祖先
  3. stk:1 4 10 同上
  4. stk:1 4 7 11 此時加入11,11和棧頂10的祖ca是7,而4的深度小於七,那麼把棧頂改爲7,加入11
  5. stk:1 4 9 此時加入9,9和棧頂11的lca是4,將11和7出棧,此時棧頂就是lca,無需改動,加入9
  6. stk:1 5 此時加入5,5和棧頂9的lca是1,將4和9出棧,此時棧頂就是lca,無需改動,加入5
  7. stk:1 此時將棧內元素出完

現在看看建出的樹:
1 無
2 無
3 無
4 7->10
5 7->11 4->7
6 4->9 1->4
7 1->5

畫出來長這樣 :

在這裏插入圖片描述
少了一堆點,效率up

時間複雜度分析:

剛纔講了只需要處理相鄰兩點的lca,即加入不超過w-1(w是詢問點數)個lca。
那麼每次詢問只需要加入不超過2w-1個點。

總時間複雜度爲O(i=1qwi×logn)O(\sum_{i=1}^qw_i\times logn),於是就可以快樂地解決問題了。


技巧:

建完虛樹清除圖是個問題,畢竟也不可能memset,瞬間上天。。。
我們可以dfs一邊,每到一個點就清除其head,就不需要耗費過多時間。

還有一種方法,是類似迭代實現,就不需要建邊。我們在建樹過程中其實就是在遍歷虛樹,那麼我們只要把加邊操作替換爲更新父節點信息,出棧時更新ans或者直接輸出一號節點信息(看題目)即可。


代碼:

我是用樹剖求lca的。

#include<bits/stdc++.h>
using namespace std;
int dfn[250010],dep[250010],siz[250010],n,q;
int top[250010],fa[250010],stk[250010],son[250010],tot,tp;
int e[500010],w[500010],nxt[500010],head[250010],cnt;
int a[250010];
void add(int x,int y,int z){
	cnt++;
	e[cnt]=y;
	w[cnt]=z;
	nxt[cnt]=head[x];
	head[x]=cnt;
}
void dfs1(int x,int f){
	fa[x]=f;
	siz[x]=1;
	dfn[x]=++tot;
	for(int i=head[x];i;i=nxt[i])
	if(e[i]!=f){
		int y=e[i];
		dep[y]=dep[x]+1;
		dfs1(y,x);
		siz[x]+=siz[y];
		if(siz[y]>siz[son[x]])
		son[x]=y;
	}
}
void dfs2(int x,int t){
	top[x]=t;
	if(son[x]==0) return;
	else dfs2(son[x],t);
	for(int i=head[x];i;i=nxt[i])
	if(top[e[i]]==0) dfs2(e[i],e[i]);
}
int lca(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	return x;
}
void add1(int x,int y){
	//根據題目自行填充 
}
void ins(int x){
	if(tp==1){//就是特判一下卡卡常而已
		stk[++tp]=x;
		return;
	}
	int l=lca(stk[tp],x);
	if(l==stk[tp]){
		stk[++tp]=x;
		return;
	}
	while(tp>1&&dfn[stk[tp-1]]>=dfn[l]) 
	add1(stk[tp-1],stk[tp]),tp--;
    if(l!=stk[tp]){
    	add1(l,stk[tp]);
		stk[tp]=l;
	}
    stk[++tp]=x;
}
bool cmp(int x,int y){
	return dfn[x]<dfn[y];
}
int main(){
	int x,y,z,s;
	scanf("%d",&n);
	for(int i=1;i<n;i++){
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z),add(y,x,z);
	}
	dfs1(1,-1);
	dfs2(1,1);
	scanf("%d",&q);
	for(int i=1;i<=q;i++){ 
		stk[tp=1]=1;
		scanf("%d",&s);
		for(int j=1;j<=s;j++)
		scanf("%d",&a[j]);
		sort(a+1,a+s+1,cmp);
		for(int j=1;j<=s;j++) ins(a[j]);
		while(tp) add1(stk[tp-1],stk[tp]),tp--;
	}
}

例題:

  1. P2495 [SDOI2011]消耗戰
    模板題,建虛樹,求一下樹根到每個點的經過的邊的邊權最小值minn,e代表兒子
    dp[x]=min(minn[x],i=e[x]minn[i])dp[x]=min(minn[x],\sum_{i=e[x]}minn[i])

不難,請讀者自行思考。
代碼:(這裏是迭代實現)

#include<bits/stdc++.h>
using namespace std;
int dfn[250010],dep[250010],siz[250010],n,q;
int top[250010],fa[250010],stk[250010],son[250010],tot,tp;
int e[500010],w[500010],nxt[500010],head[250010],cnt;
int a[250010],minn[250010];
long long sum[250010];
void add(int x,int y,int z){
	cnt++;
	e[cnt]=y;
	w[cnt]=z;
	nxt[cnt]=head[x];
	head[x]=cnt;
}
void dfs1(int x,int f){
	fa[x]=f;
	siz[x]=1;
	dfn[x]=++tot;
	for(int i=head[x];i;i=nxt[i])
	if(e[i]!=f){
		int y=e[i];
		dep[y]=dep[x]+1;
		minn[y]=min(minn[x],w[i]);
		dfs1(y,x);
		siz[x]+=siz[y];
		if(siz[y]>siz[son[x]])
		son[x]=y;
	}
}
void dfs2(int x,int t){
	top[x]=t;
	if(son[x]==0) return;
	else dfs2(son[x],t);
	for(int i=head[x];i;i=nxt[i])
	if(top[e[i]]==0) dfs2(e[i],e[i]);
}
int lca(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	return x;
}
void add1(int x,int y){
	//cout<<x<<" "<<y<<endl;
	if(x==y) return;
	sum[x]+=min(sum[y],minn[y]+0ll);
}
void ins(int x){
	if(tp==1){
		sum[x]=minn[x];
		stk[++tp]=x;
		return;
	}
	int l=lca(stk[tp],x);
	//cout<<stk[tp]<<" "<<x<<" lca:"<<l<<endl;
	if(l==stk[tp]) return;
	while(tp>1&&dfn[stk[tp-1]]>=dfn[l]) 
	add1(stk[tp-1],stk[tp]),tp--;
    if(l!=stk[tp]){
    	sum[l]=0;
    	add1(l,stk[tp]);
		stk[tp]=l;
	}
	sum[x]=minn[x];
    stk[++tp]=x;
}
bool cmp(int x,int y){
	return dfn[x]<dfn[y];
}
int main(){
	int x,y,z,s;
	scanf("%d",&n);
	for(int i=1;i<n;i++){
		scanf("%d%d%d",&x,&y,&z);
		add(x,y,z),add(y,x,z);
	}
	minn[1]=2e9;
	dfs1(1,-1);
	dfs2(1,1);
	scanf("%d",&q);
	for(int i=1;i<=q;i++){ 
		sum[1]=0;
		stk[tp=1]=1;
		scanf("%d",&s);
		for(int j=1;j<=s;j++)
		scanf("%d",&a[j]);
		sort(a+1,a+s+1,cmp);
		for(int j=1;j<=s;j++) ins(a[j]);
		while(tp) add1(stk[tp-1],stk[tp]),tp--;
		printf("%lld\n",sum[1]);
	}
}
  1. 挖坑
發佈了27 篇原創文章 · 獲贊 5 · 訪問量 2758
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章