1553:【例 2】暗的連鎖 樹上差分

題目鏈接:http://ybt.ssoier.cn:8088/problem_show.php?pid=1553
差分能夠更好的解決區間問題。
在講樹上差分之前,首先需要知道樹的以下兩個性質:

(1)任意兩個節點之間有且只有一條路徑。

(2)根節點確定時,一個節點只有一個父親節點
這兩個性質都很容易證明。那麼我們知道,如果假設我們要考慮的是從u到v的路徑,u與v的lca是a,那麼很明顯,如果路徑中有一點u′已經被訪問了,且u′≠a,那麼u’的父親也一定會被訪問,這是根據以上性質可以推出的。所以,我們可以將路徑拆分成兩條鏈,u->a和a->v。那麼樹上差分有兩種常見形式:(1)關於邊的差分;(2)關於節點的差分。

①關於邊的差分:
將邊拆成兩條鏈之後,我們便可以像差分一樣來找到路徑了。用cf[i]
代表從i到i的父親這一條路徑經過的次數。因爲關於邊的差分,a是不在其中的,所以考慮鏈u->a,則就要使cf[u]++,cf[a]−−。然後鏈a->v,也是cf[v]++,cf[a]−−。所以合起來便是cf[u]++,cf[v]++,cf[a]−=2。然後,從根節點,對於每一個節點x,都有如下的步驟:
1)枚舉x的所有子節點u
2)dfs所有子節點u
3)cf[x]+=cf[u]
那麼,爲什麼能夠保證這樣所有的邊都能夠遍歷到呢?因爲我們剛剛已經說了,如果路徑中有一點u′已經被訪問了,且u′≠a,那麼u′的父親也一定會被訪問。所以u′被訪問幾次,它的父親也就因爲u′被訪問了幾次。所以就能夠找出所有被訪問的邊與訪問的次數了。路徑求交等一系列問題就是通過這個來解決的。因爲每個點都只會遍歷一次,所以其時間複雜度爲Θ(n)
②關於點的差分:
還是與和邊的差分一樣,對於所要求的路徑,拆分成兩條鏈。步驟也和上面一樣,但是也有一些不同,因爲關於點,u與v的lca是需要包括進去的,所以要把lca包括在某一條鏈中,用cf[i]表示i被訪問的次數。最後對cf數組的操作便是cf[u]++,cf[v]++,cf[a]−−,cf[father[a]]−−。其時間複雜度也是一樣的Θ(n).

本題就是就是對邊的差分,首先題目給的是一顆樹,然後給我們m條附加邊(非樹邊),在每一條非樹邊(x,y)添加到樹邊中,就會在樹上(x,y)路徑形成一個環。

因而我們每次讀入一條附加邊,就稱就給x到y的路徑上的所有主要邊記錄上“被覆蓋一次”,這樣再去遍歷所有主要邊。這樣題目就變成了**給定一張無向圖和一顆生成樹,求每條“樹邊”被“非樹邊”覆蓋了多少次。

對於我們想要切割的一條主要邊,有以下3種情況

若這條邊被覆蓋0次,則可以任意再切斷一條附加邊
若這條邊被覆蓋1次,那麼只能再切斷唯一的一條附加邊
若這條邊被覆蓋2次及以上,沒有可行的方案

所以我們求的被覆蓋次樹就是上述樹上差分求的cf[]。
/*我們給樹上每個點一個初始爲0的權值,然後對每條非樹邊(x,y),令節點x,y的權值+1,節點lca(x,y)的權值-=2,最後深度優先遍歷,求出f[x]表示以x爲根節點的子樹中各個點的權值和。f[x]就是x與父節點之間被“樹邊”覆蓋的次數,時間複雜度爲O(N+M)。

PS:本題對應的是樹上邊差分,我們最後dif[lca(u,v)]所得是lca(u,v)和pre[lca(u,v)][0]兩點所在的邊;另一種差分是點差分,我們就不能用dif[lca(u,v)]-=2,而是dif[lca(u,v)]–,dif[lca(pre[lca(u,v)][0])]–,因爲lca(u,v)也在u…v這條路徑上,它同樣需要被加x。回溯的時候會從u和v兩個方向都給lca(u,v)加一個x,而它只能加一個,因此dif[lca(u,v)]-=x。而lca(u,v)的爸爸則根本無法被加,在lca(u,v)已經只加一個x了,因此dif[pre[lca(u,v)]]-=x就能讓lca(u,v)的爸爸不加x

給出ac代碼:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7,M = 4e5+7;
int n,m,cnt;
int head[M],ver[M],nex[M];
int depth[N],pre[N][22],vis[N],sta[N],dif[N],lg[N];
void add(int x,int y){
	ver[++cnt]  = y;
	nex[cnt] = head[x];
	head[x] = cnt;
}
void read(){
	scanf("%d%d",&n,&m);
	for(int i = 1,x,y;i < n;i++){
		scanf("%d%d",&x,&y);
		add(x,y),add(y,x);
	}
}
void lca_dfs(int f,int fa){
	depth[f] = depth[fa] + 1;
	pre[f][0] = fa;
	for(int i=1;(1<<i) <= depth[f] ; i++)
	pre[f][i] = pre[pre[f][i-1]][i-1];
	
	for(int i=head[f];i;i=nex[i])
	if(ver[i]!= fa)
	lca_dfs(ver[i],f);
}
int lca(int x,int y){
	if(depth[x] < depth[y]) swap(x,y);
	while(depth[x] > depth[y])
	x = pre[x][lg[depth[x] - depth[y]]-1 ];
	if(x==y) return x;
	for(int i=lg[depth[x]]-1;i>=0;i--)
	if(pre[x][i] != pre[y][i])
	x = pre[x][i],y = pre[y][i];
	
	return pre[x][0];
}
int dfs(int f){
	sta[f] = dif[f];
	vis[f] = 1;
	for(int i=head[f];i;i=nex[i]){
		int y = ver[i];
		if(vis[y]) continue;
		sta[f] += dfs(y);
	}
	return sta[f];
}
void solve(){
	for(int i=1;i<=n;i++)
	lg[i] = lg[i-1] + (1<<lg[i-1] == i);
	lca_dfs(1,0);
	for(int i=1,x,y;i<=m;i++){
		scanf("%d%d",&x,&y);
		 dif[x] ++;
		 dif[y] ++;
		 dif[lca(x,y)] -= 2;
	}
	dfs(1);
	int ans = 0;
	for(int i=1;i<=n;i++){
		if(sta[i]==0 && i!=1) ans += m;
		if(sta[i]==1) ans ++;
	}
	printf("%d\n",ans);
}
int main(){
	read();
	solve();
	return 0;
}

參考文獻:
https://www.cnblogs.com/ice-wing/p/7709311.html
李煜東 算法競賽進階指南

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章