【SGU109】
世界著名的魔術師大衛科波菲爾(David Copperfield喜歡)給人們看這個把戲:一個N*N的的有着不同顏色塊的圖片在電視屏幕上,讓我們給整張圖片按照下面的方式編號
1 2 ... N
N+1 N+2 ... 2N
N*(N-1)+1 N*(N-1)+2 ... N*N
所有觀衆首先將手指指向左上角的小格(1號圖案)然後魔術開始:魔術師命令觀衆在圖案中移動手指 K1次(一次移動是指移動到當前圖案上下左右的另一個圖案),之後魔術師輕揮手指,有一些圖案所在的格子不見了(被刪掉的格子以後不能經過),魔術師解釋說"你不可能在這裏!", ....他說對了 - 你的手指並不在他刪掉的任一格子上。接着他再讓觀衆移動手指K2次, 等等. 最後他刪得只剩下1個格子,面帶微笑地宣佈勝利 "我抓到你了!" (鼓掌....).
剛纔,大衛嘗試着重複這個魔術,不幸的是,他昨天很累,你瞭解當人在頭痛的時候變魔術師十分困難的。你需要編寫一個程序來幫助大衛科波菲爾來完成。
【輸入】
包括一個整數
【輸出】
你的程序應該輸出如下的文件:
k1 x1,1 x1,2 …… x1,m1
k2 x2,1 x2,2 …… x2,m2
……
ke xe,1 xe,2 …… xe,me
ki——表示觀衆們第i次需要移動ki次(N<=ki<=300)每一個ki都是不懂的 xi.1 ~ xi,mi是觀衆完成第ki次後科波菲爾需要刪掉的團的編號,每一輪不能不刪圖案,每一個圖案不能被重複刪兩次,最後只能留下一個圖案
這是一道很奇怪的題目。我想了很久纔想到副對角線的做法,然而大神們又給出了更加高明的構造技巧
構造類的好題目
答案可能有很多種,我們需要構造出一組可行解
【方案一】:(奇偶性分析)從樣例着手
樣例的操作如下
1 2 3
4 5 6
7 8 9
移動3次後 手指不可能落在奇數上,我們可以把最外層的奇數刪掉用 * 表示
* 2 *
4 5 6
* 8 *
接着在移動5步,肯定不會落在偶數上面,那麼最外層的偶數可以去掉了
就變成了
* * *
* 5 *
* * *
可以自己腦補出來,自己的
這是較爲簡單的情形,n爲偶數的時候稍微複雜一些。
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
(移動5次)
* 2 * 4
5 6 7 *
* 10 11 12
13 * 15 *
(移動7次)
* * * *
* 6 7 *
* 10 11 *
* * * *
變成中間4個數的情況,應該先移動偶數步把7去掉,然後在移動奇數步把6 11 去掉
總結
第奇數次走了奇數步時 將最外層行數+列數爲偶數的數字去掉,第偶數次移動奇數步時,將最外層行數+列數爲奇數的數字去掉,當n爲偶數時,最後剩下的4個數字要特殊處理
額好吧,下面還有各種喪病方案
【方案二】: 染色法(其實這個和方案一的本質是一樣的)
我們的方式已確定爲第一次讓玩家走N步,之後的步數會逐漸增加。
我們把棋盤黑白染色。
易證,走奇數步時會走到另一種顏色上,所以我們只需要每次讓玩家走到另一種顏色上去,把相反的顏色的格子刪掉。
但是我們不能這麼刪點,有如下兩個問題:
1.如果n一開始爲偶數
2.刪相反色的格子時候,萬一形成了“斷路”,把某些玩家孤立在一個地方(你不能讓他們剁手)。
首先,第一點很好處理,如果爲偶數,則先把周圍一圈格子刪掉,然後+1,把偶數轉換成奇數,繼續做。
至於第二點,則是本題的重點,我們需要找到一種切實可行的方法刪除格子保證不會出現問題2。
可以腦補一下,像剝皮一樣,一圈一圈地刪除,那麼就能把玩家困在中間某個格子的情形排除了。
具體做法:
(曼哈頓距離)橫座標之差+縱座標之差
首先,先走n步,把大於與左上角格子曼哈頓距離的點刪掉,因爲這些格子是達不到的。
然後 如果爲偶數 +1 轉換成奇數
如果爲奇數 +2
設一個dist爲每次曼哈頓距離與當前點的差,一層一層向內刪除格子,直到dist<=2,玩家已經被困在中間格子內。
【方法三】 高端(wode)構造
按副對角線去掉數字,最後留下1的位置。
副對角線 從左下至右上的數歸爲副對角線
對於一、二方法有如下代碼
#include<cstdio>
int n;
int main()
{
scanf("%d",&n);
printf("%d",n);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (i-1+j-1>n) printf(" %d",(i-1)*n+j);
printf("\n");
int dist=n+2;
int now=n;
while (dist>2)
{
now++;
while (now%2==0) now++;
printf("%d",now);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (i+j==dist) printf(" %d",(i-1)*n+j);
printf("\n");
dist--;
}
}
對於方案三 副對角線法又有如下代碼
#include <stdio.h>
#include <stdlib.h>
#define MAX 200
int n,i,j,k;
void open()
{
freopen("109.in","r",stdin);
freopen("109.out","w",stdout);
}
void close()
{
fclose(stdin);
fclose(stdout);
}
void init()
{
scanf("%d",&n);
}
void work()
{
if (n==2) //特判
{
printf("3 4\n5 2 3\n");
}
else
{
printf("%d",k=n);
for (i=2;i<n;i++)
for (j=n-i+1;j<n;j++)
printf(" %d",i*n+j+1);
printf("\n%d",k+=1+(n&1));
for (j=0;j<n-1;j++)
printf(" %d",n*2+j*(n-1));
for (k+=2,i=n;k<3*n+1;k+=2,i--)
{
printf("\n%d",k);
for (j=0;j<i;j++)
printf(" %d",i+j*(n-1));
}
}
}
int main()
{
open();
init();
work();
close();
return 0;
}