1. 程式人生 > 其它 >Codeforces 891D - Sloth(換根 dp)

Codeforces 891D - Sloth(換根 dp)

樹存在完美匹配的等價表達+換根 dp

Codeforces 題面傳送門 & 洛谷題面傳送門

換根 dp 好題。

為啥沒人做/yiw

首先 \(n\) 為奇數時答案顯然為 \(0\),證明顯然。接下來我們著重探討 \(n\) 是偶數的情況。

考慮一棵樹存在完美匹配的等價證明:我們考察每一條邊,如果刪掉該條邊後兩個連通塊的大小都是奇數,那麼顯然我們如果貪心地對兩個連通塊進行二分圖完美匹配,如果還剩至少三個點沒被匹配,那麼顯然原圖不存在二分圖完美匹配,否則我們肯定會剩下該連通塊的根節點,也就是這條邊的一個端點。換句話,如果原圖存在二分圖完美匹配,那麼這樣的邊必然在完美匹配中。而另一方面,對於所有“刪掉這條邊後,形成的兩個連通塊的大小都是偶數”的邊,顯然如果原圖存在完美匹配那麼我們對兩個連通塊貪心地二分圖匹配後,必然是能完全匹配完的,因此這樣的邊不可能出現在完美匹配中。也就是說:

Lemma. 如果一張圖存在完美匹配,那麼完美匹配中的邊集必定是所有滿足“割掉這條邊後形成的兩個連通塊大小均為奇數”的邊。

也就是說,一棵樹存在完美匹配,當且僅當所有滿足“割掉這條邊後形成的兩個連通塊大小均為奇數”形成一個匹配。

考慮根據這個性質對原圖的邊進行染色,我們稱一條邊為黑邊,當且僅當刪掉這條邊後形成的兩個連通塊大小均為奇數。反之稱其為白邊,那麼考慮刪掉一條邊並加入一條新邊後每條邊的黑白型別的變化情況。我們假設新加入的邊的兩個端點為 \((u,v)\),那麼顯然你刪除的邊在原圖上必須在 \(u\to v\) 的路徑上,否則最後形成不了樹。那麼對於一個不在 \(u\to v\)​ 的路徑上的邊,割掉它後顯然不包含 \(u,v\)

的這個連通塊不會發生變化,因此它的變型也不會發生變化,而對於在 \(u\to v\) 路徑上的邊,我們假設 \(u’,v’\) 為割掉的邊,那麼如果這條邊在 \(u\to u’\) 上,那麼在加入新邊前後,割掉這條邊後,樹所形成的兩個連通塊之一會扣掉 \(v\) 原來所在的連通塊,因此如果割掉的這條邊本身是黑邊,那麼 \(v\) 原來所在的連通塊的大小為奇數,因此原來 \(u\to v\) 的路徑上的所有邊型別都會翻轉,反之 \(u\to v\) 路徑上所有邊型別不會發生變化。

感覺這一段講得有點懸乎,藉助下圖可能比較好理解(綠邊為原樹上的邊,橙邊為割掉的邊,紅邊為加上的邊,填好色的三角形為一個子樹,那麼對於不在 \(B\to G\)

​ 路徑上的邊,如 \(AB\)​,操作前後形成的樹中,刪掉這條邊之後形成的連通塊之一都是綠色的子樹,\(CH\) 也同理,因此邊的型別不發生變化,而對於 \(B\to G\) 路徑上的邊,如 \(BC\),原來割掉它之後形成的連通塊之一是 \(B\) 加上綠色的子樹,而刪掉 \(DE\) 加上 \(BG\)​ 之後形成的連通塊之一是 \(B,A\) 所在的子樹 \(+\) 藍色的子樹,因此如果藍色子樹大小為奇數,即割掉的邊為黑邊那這條邊型別就會發生變化,否則這條邊型別不變)

因此我們將割掉的邊分為黑邊和白邊兩種型別。如果割掉的邊為白邊,由於所有邊顏色都不變,因此如果原圖中存在完美匹配,那麼隨便連上一條邊仍然存在完美匹配,貢獻就是兩個子樹的 \(siz\) 之積。否則不論連上什麼邊都不符合條件。如果割掉的邊為黑邊,這種情況有點繁瑣,考慮分情況討論。首先每個點連出去的黑邊個數都是奇數對吧,否則總點數就是奇數了,因此

  • 如果一個點連出去了 \(5\) 條及以上的黑邊,那麼由於一條路徑最多破壞掉一個點相鄰的兩個黑邊,因此答案肯定為 \(0\)

  • 否則如果存在一個點連出去了三條黑邊,那麼顯然我們這條路徑必須破壞掉所有與連出去至少三條黑邊的點,同時也不能產生新的連出去至少三條黑邊的點,稍加思考即可發現這等價於:

    • \(c\) 表示這條路徑上相鄰兩條邊都是黑邊的對數,那麼必須有 \(c\) 等於 \(\text{連出去至少三條黑邊的點的個數}\),否則破壞不掉所有不合法的點
    • 不能存在相鄰兩條邊,滿足它們都是白邊,否則反轉之後就會得到兩條相鄰的黑邊。
    • 路徑端點的兩條邊必須都是黑邊,否則翻轉後端點相連的邊變成黑邊,而顯然你新加入的邊也是黑邊,就會使端點變為不合法。

    答案就是所有符合以上三條限制的路徑上黑邊個數之和。我們考慮怎樣求這東西。我們隨便令一個連出至少三條黑邊的點 \(r\) 為根,然後遍歷它的三個黑邊連出去的子樹,記 \(cnt_v\) 表示 \(r\to v\) 路徑上有多少對相鄰的黑邊,\(ban_v\) 表示 \(r\to v\) 路徑上是否有相鄰的白邊,\(d_v\) 表示 \(r\to v\) 路徑上黑邊個數。那麼如果我們求出這三個陣列後,我們就可以得出一條路徑 \(u\to v\) 符合條件當且僅當:

    • \(ban_u=0,ban_v=0\)
    • \(u,v\)\(r\) 的不同子樹內,且 \(u,v\) 所在 \(r\) 的子樹的根節點與 \(r\) 相連的邊都是黑邊
    • \(cnt_u+cnt_v\) 等於連出去至少三條黑邊的點的個數 \(-1\),至於為什麼減一是因為 \(r\) 也會被破壞而我們沒有統計進去。
    • \(u,v\) 與其父親相連的邊都是黑邊

    這樣的路徑的貢獻就是 \(d_u+d_v\),用類似點分治的方式統計貢獻即可

  • 如果不存在一個點連出去了至少三條黑邊,也即原圖就存在完美匹配那麼我們隨便找一條黑白相間且兩個端點相連的邊都是黑邊的路徑翻轉即可,貢獻也是路徑上黑邊個數。這個可以通過換根 \(dp\) 實現,\(cnt_{u,0/1}\) 表示 \(u\) 子樹內有多少條路徑 \(v\to u\),滿足與 \(v\) 相連的邊為黑邊,與 \(u\) 相連的邊為白邊/黑邊,\(sum_{u,0/1}\) 則表示所有 路徑 \(v\to u\),滿足與 \(v\) 相連的邊為黑邊,與 \(u\) 相連的邊為白邊/黑邊,這樣的路徑上黑邊個數之和,換根 DP 一下即可。

複雜度線性。

題解碼死我了

const int MAXN=5e5;
int n,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=1;
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int siz[MAXN+5],fa[MAXN+5],bad[MAXN+5],ed[MAXN+5];
bool isbad[MAXN+5];
void dfs(int x,int f){
	fa[x]=f;siz[x]=1;
	for(int e=hd[x];e;e=nxt[e]){
		int y=to[e];if(y==f) continue;
		dfs(y,x);siz[x]+=siz[y];ed[y]=e>>1;
	}
}
namespace have{
	ll sum[MAXN+5][2],sum_out[MAXN+5];
	int cnt[MAXN+5][2],cnt_out[MAXN+5];
	void dfs1(int x,int f){
		for(int e=hd[x];e;e=nxt[e]){
			int y=to[e];if(y==f) continue;dfs1(y,x);
			if(isbad[e>>1]){
				sum[x][1]+=sum[y][0]+cnt[y][0]+1;
				cnt[x][1]+=cnt[y][0]+1;
			} else {
				sum[x][0]+=sum[y][1];
				cnt[x][0]+=cnt[y][1];
			}
		} //printf("%d %lld %d %lld %d\n",x,sum[x][0],cnt[x][0],sum[x][1],cnt[x][1]);
	}
	void dfs2(int x,int f){
		ll tmp_sum[3]={0},tmp_cnt[3]={0};
		for(int e=hd[x];e;e=nxt[e]){
			int y=to[e];
			if(y==f){
				if(isbad[e>>1]){
					tmp_sum[1]+=sum_out[x];
					tmp_cnt[1]+=cnt_out[x];
				} else {
					tmp_sum[0]+=sum_out[x];
					tmp_cnt[0]+=cnt_out[x];
				}
			} else {
				if(isbad[e>>1]){
					tmp_sum[1]+=sum[y][0]+cnt[y][0]+1;
					tmp_cnt[1]+=cnt[y][0]+1;
				} else {
					tmp_sum[0]+=sum[y][1];
					tmp_cnt[0]+=cnt[y][1];
				}
			}
		}
		for(int e=hd[x];e;e=nxt[e]){
			int y=to[e];if(y==f) continue;
			if(isbad[e>>1]){
				tmp_sum[1]-=sum[y][0]+cnt[y][0]+1;
				tmp_cnt[1]-=cnt[y][0]+1;
				sum_out[y]=tmp_sum[0]+tmp_cnt[0]+1;
				cnt_out[y]=tmp_cnt[0]+1;
				tmp_sum[1]+=sum[y][0]+cnt[y][0]+1;
				tmp_cnt[1]+=cnt[y][0]+1;
			} else {
				tmp_sum[0]-=sum[y][1];
				tmp_cnt[0]-=cnt[y][1];
				sum_out[y]=tmp_sum[1];
				cnt_out[y]=tmp_cnt[1];
				tmp_sum[0]+=sum[y][1];
				tmp_cnt[0]+=cnt[y][1];
			} dfs2(y,x);
		} //printf("%d %lld %d\n",x,sum_out[x],cnt_out[x]);
	}
	void solve(){
		ll res=0,ss=0;
		for(int i=1;i<=n;i++) if(~siz[i]&1) res+=1ll*siz[i]*(n-siz[i]);
		dfs1(1,0);dfs2(1,0);
		for(int i=1;i<=n;i++){
			ss+=sum[i][1];
			if(siz[i]&1) ss+=sum_out[i];
		} ss>>=1;
		printf("%lld\n",ss+res);
	}
}
namespace doesnt_have{
	bool ban[MAXN+5];int cnt[MAXN+5],siz_[MAXN+5],d[MAXN+5];
	vector<int> pt;ll buc[MAXN+5],bucc[MAXN+5];
	void dfs0(int x,int f){
		siz_[x]=1;
		for(int e=hd[x];e;e=nxt[e]) if(to[e]^f){
			dfs0(to[e],x);siz_[x]+=siz_[to[e]];
		}
	}
	void dfsclc(int x,int f,int pre){
		pt.pb(x);
		for(int e=hd[x];e;e=nxt[e]){
			int y=to[e];if(y==f) continue;
			cnt[y]=cnt[x]+(pre&&(isbad[e>>1]));
			ban[y]=ban[x]|(!pre&&(!isbad[e>>1]));
			d[y]=d[x]+(siz_[y]&1);dfsclc(y,x,isbad[e>>1]);
		}
	}
	void solve(){
		int rt=0,tot=0;ll res=0;
		for(int i=1;i<=n;i++) if(bad[i]>=3) rt=i,tot++;
		dfs0(rt,0);//printf("%d\n",rt);
		for(int e=hd[rt];e;e=nxt[e]){
			int y=to[e];
			if(isbad[e>>1]){
				pt.clear();d[y]=1;cnt[y]=0;dfsclc(y,rt,1);
//				for(int z:pt) printf("%d %d %d %d\n",z,cnt[z],ban[z],d[z]);
				for(int z:pt) if((siz_[z]&1)&&!ban[z]&&bad[z]==1) res+=buc[tot-1-cnt[z]]+1ll*bucc[tot-1-cnt[z]]*d[z];
				for(int z:pt) if((siz_[z]&1)&&!ban[z]&&bad[z]==1) buc[cnt[z]]+=d[z],bucc[cnt[z]]++;
			}
		} printf("%lld\n",res);
	}
}
int main(){
	scanf("%d",&n);if(n&1) return puts("0"),0;
	for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
	dfs(1,0);for(int i=2;i<=n;i++) if(siz[i]&1) bad[i]++,bad[fa[i]]++,isbad[ed[i]]=1;
	bool flg=1;
	for(int i=1;i<=n;i++){
		if(bad[i]>=5) return puts("0"),0;
		if(bad[i]>=3) flg=0;
	}
	if(flg) have::solve();
	else doesnt_have::solve();
	return 0;
}
/*
8
1 2
1 3
2 4
2 5
3 6
3 7
7 8

10
1 2
1 3
1 4
2 5
2 6
6 7
5 8
5 9
2 10

14
1 2
1 3
1 4
2 5
2 6
6 7
5 8
5 9
2 10
9 11
8 12
8 13
12 14

16
1 2
1 3
1 4
2 5
2 6
6 7
5 8
5 9
2 10
9 11
8 12
8 13
12 14
13 15
13 16

10
1 2
3 2
3 4
4 5
5 6
6 7
7 8
7 9
6 10
*/