生成樹(構造)


NOIP 模擬賽的題目,先貼上題面。

題目描述

我們知道,生成樹是圖 \(G\) 的一個子集,它的所有頂點都被盡可能少的邊覆蓋。因此,生成樹沒有環,也不能是多個聯通塊。

顯然,我們知道一個圖的生成樹是不唯一的。

給定一個任意的無向簡單圖(沒有重邊和自環),我們現在定義生成樹去除:找到該圖的任意一棵生成樹,並從原始圖中去除生成樹包含的所有邊,得到一個新圖。

Cuber QQ 發現“生成樹去除”非常有趣,以至於他想一遍又一遍地做。

顯然,他想從一個完全圖(每對不同的頂點都通過唯一的邊連接的圖)開始。通過巧妙的選擇要移除的生成樹,以便可以重復移除盡可能多的次數,直到圖中不再有生成樹。

輸入格式

輸入第一行包含一個整數 \(T\),表示測試數據的數量。

每組測試數據包含一行,一個整數 \(n\) 表示完全圖的點數。

輸出格式

對於每個測試用例,輸出第一行包含 Case #x: y,其中 \(x\) 是從 \(1\) 開始的測試用例編號,\(y\) 是最多可以進行刪除的次數。

接下來的 \(y\times (n-1)\) 行,從 \((n-1)\times (i+1)+1\) 行到 \((n-1)\times i\) 行,輸出你第 \(i\) 次決定刪除的生成樹。每行包含兩個數字 \(u\)\(v(1\le u,v\le n,u\ne v)\)\((u,v)\) 應該是有效的樹邊,並且與之前被移除的邊不重合。

如果有多個解,輸出其中任何一個。

數據范圍

對於 \(20\%\) 的數據,\(n\le 20\)

對於 \(50\%\) 的數據,\(n\le 50\)

對於 \(100\%\) 的數據,\(1\le T\le 500,2\le n\le 1000,\sum n\le 1000\)

解題思路

“刪除次數”可以看作提示——手玩樣例發現,\(n=2\)\(n=3\) 時,刪除次數為 \(1\)\(n=4\)\(n=5\) 時,刪除次數為 \(2\)\(n=6\)\(n=7\) 時,刪除次數為 \(3\)。我們猜測,對於 \(n\) 個點的完全圖(\(n\) 為偶數),刪除次數為 \(\frac{n}{2}\),而奇數點的情況可以減一轉化為偶數點的情況

對於偶數點的情況,考慮怎樣構造方案。滿足既不能成環,又能連通的方案不唯一,這里給出其中一種:將有 \(n\) 個點的完全圖畫成一個圓,從前 \(\frac{n}{2}\) 個點出發,“反復橫跳”,即向右一步、向左兩步、向右三步、向左四步……如下圖所示:

image

以從 \(1\) 開始為例,\(1\to 2 \to 6 \to 3 \to 5 \cdots\)。這樣連出的生成樹是一條鏈:

image

mspaint 比較糊……至此,我們解決了偶數點的問題,考慮怎樣將奇數點轉化為偶數點。可以認為奇數點就是在上圖的基礎上增加了一個 \(n+1\) 號點,我們只需將前 \(\frac{n}{2}\) 個點分別向 \(n+1\) 號點連一條邊即可。

代碼實現

直接上代碼(很短);

void main() {//包在namespace里才這么寫的
	int T;
	scanf("%d",&T);
	for(int test(1); test<=T; ++test) {
		int n;
		bool odd=false;
		scanf("%d",&n);
		printf("Case #%d: %d\n",test,n>>1);
		if(n&1) --n,odd=true;//直接轉化為偶數情況

		for(int i=0; i<(n>>1); ++i) {
			int x=i,d=1,f=1;//d是每一步的大小,f控制往左還是往右走,因為是環,所以越界時可以直接mod n
			while(d<n) {
				printf("%d %d\n",x+1,(x+d*f+n)%n+1);//避免mod時出現0
				x+=d*f,x=(x+n)%n,++d,f=-f;
			}
			if(odd) printf("%d %d\n",i+1,n+1);//若為奇數,則與n+1號點連邊
		}
	}
}

THE END


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM