1. 程式人生 > >[JZOJ5969] 世界線修理(歐拉回路)

[JZOJ5969] 世界線修理(歐拉回路)

題目

描述

在這裡插入圖片描述>
在這裡插入圖片描述

題目大意

給你兩棵樹,讓你對每個點賦權,使得在兩棵樹中的任意子樹的和絕對值為 1 1


比賽思路

其實我一開始理解錯題意了……


正解

首先,我們可以判斷每個點權的奇偶性。
如果一個點有偶數個兒子,顯然它們的和為偶數,所以這個點權為奇數。
如果一個點有奇數個兒子,顯然它們的和為奇數,所以這個點權為偶數。
所以在兩棵樹中,分別計算它兒子的個數。如果它們的奇偶性不一樣,那麼一定impossible。

現在有一個問題,如果它們的奇偶性一樣,那麼是否一定有一種可行的方案呢?
的確是可行的。
然後發現必定有一種可行的方案滿足每個點的權值只能為 1 , 0 , 1

-1,0,1
感性理解一下可能就出來了。

咳咳,先不要討論這麼多。
接下來有一個巧妙的東西,叫歐拉回路
我也不知道出題人的腦洞究竟有多大,這題居然可以和歐拉回路建立起聯絡!
首先,我們把兩個樹構出來。其中對於根節點,我們用一個超級源來連線它們。
顯然對於每一個度數為偶數的點,因為它們有奇數個兒子,所以它們的權值為 0 0
反之,對於每一個度數為奇數的點,它們的權值為

1 -1 1 1
然後列舉每一個點,如果這個點的度數為奇數,那麼就將兩個樹中對應的點連線起來。(這條邊叫橫插邊)
然後我們就發現這個圖中,每個點的度數都是偶數,那麼就一定存在一個歐拉回路。
隨便從一個點出發,找出一條歐拉回路。
隨便以某個樹為準,對於每一個橫插邊,看看它們的方向,如果是進入這棵樹,那麼這個橫插邊對應的兩點的點權為 1 -1 ,否則為 1 1
當然,反過來也一樣。
然後答案就出來了。

能想出這個解法的人一定是個天才……
接下來我們來證明這個解法為什麼是正確的。
首先設四類環:
一類環為,從某個點開始,先走兒子邊,然後從兒子邊回來。
二類環為,從某個點開始,先走兒子邊,然後從父親邊回來。
三類環為,從某個點開始,先走橫插邊,然後從父親邊回來。
四類環為,從某個點開始,先走兒子邊,然後從橫插邊回來。
當然,每個環的走向反過來都是等價的。
然後考慮每個環的代價:
對於一類環,就是在兩棵樹之間跳來跳去,但代價都是在這個點的子樹以內計算的。進來的代價為 1 -1 ,出去的代價為 1 1 ,所以一類環的代價是 0 0
四類環也是同樣的道理,代價為 0 0 .
對於二類環,也是在兩棵樹之間跳來跳去,但是,由於最後從父親邊回來,意味著最後一次從橫插邊跳回來的時候計算的代價不在這個點的子樹內,所以二類環的代價是 1 -1 1 1
三類環也是同樣的道理,代價也是 1 -1 1 1

然後我們再看看每一個點:
如果一個點有奇數個兒子,那麼它的度數為偶數,是沒有橫插邊的。所以只有若干個一類環和一個二類環,所以代價為 1 -1 1 1
如果一個點有偶數個兒子,那麼它有橫插邊。所以,要麼是若干個一類環和一個三類環,要麼是若干個一類環和一個二類環和一個四類環。然後可以發現代價都是 1 -1 1 1
得證……

還有一個問題,為什麼要設定一個超級源?
仔細想一下,設定一個超級源之後,根節點就變得跟普通點一樣了,它們的性質也一樣。
而且你會發現,如果不設超級源,那麼僅僅用這個方法並不成立,或許要加點特判什麼的……


程式碼

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100010
int n;
int fa1[N+1],fa2[N+1];
int rd1[N+1],rd2[N+1];
struct EDGE{
	int to;
	EDGE *las;
	bool bz;
} e[N*6+1];
int ne=-1;
EDGE *last[N*2+1];
#define rev(ei) (e+(int((ei)-e)^1))
inline void link(int u,int v){
	e[++ne]={v,last[u],1};
	last[u]=e+ne;
	e[++ne]={u,last[v],1};
	last[v]=e+ne;
}
int w[N*6+1],nw;
void dfs(int);
int ans[N+1];
int main(){
	scanf("%d",&n);
	for (int i=1;i<=n;++i){
		scanf("%d",&fa1[i]);
		if (fa1[i]!=-1)
			rd1[fa1[i]]++;
	}
	for (int i=1;i<=n;++i){
		scanf("%d",&fa2[i]);
		if (fa2[i]!=-1)
			rd2[fa2[i]]++;
	}
	for (int i=1;i<=n;++i)
		if ((rd1[i]^rd2[i])&1){//當奇偶性不同時
			printf("IMPOSSIBLE\n");
			return 0;
		}
	printf("POSSIBLE\n");
	for (int i=1;i<=n;++i){
		//i<<1表示i在第一棵樹的點,i<<1|1表示i在第二棵樹的點
		link(i<<1,fa1[i]!=-1?fa1[i]<<1:0);
		link(i<<1|1,fa2[i]!=-1?fa2[i]<<1|1:0);
		if (~rd1[i]&1)
			link(i<<1,i<<1|1);
	}
	w[0]=1<<1;
	dfs(1<<1);
	for (int i=1;i<=nw;++i)
		if ((w[i-1]^w[i])==1){//如果兩個點互為對應點
			if (w[i]&1)
				ans[w[i]>>1]=-1;
			else
				ans[w[i]>>1]=1;
		}	
	for (int i=1;i<=n;++i)
		printf("%d ",ans[i]);
	return 0;
}
void dfs(int x){//求歐拉回路
	for (EDGE *ei=last[x];ei;ei=last[x]){
		last[x]=ei->las;//將這條邊刪除
		if (ei->bz){
			rev(ei)->bz=ei->bz=0;
			dfs(ei->to);
		}
	}
	w[++nw]=x;
}

總結

說實話,其實感覺上這題沒什麼好總結的。
因為這題能想出來實在是太變態了。