SGU109 各類構造法 【副對角線法】【染色法】

【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;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章