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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值