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}\) 個點出發,“反復橫跳”,即向右一步、向左兩步、向右三步、向左四步……如下圖所示:
以從 \(1\) 開始為例,\(1\to 2 \to 6 \to 3 \to 5 \cdots\)。這樣連出的生成樹是一條鏈:
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號點連邊
}
}
}