SGU109 各类构造法 【副对角线法】【染色法】

来源:互联网 发布:天庭淘宝店 txt下载 编辑:程序博客网 时间:2024/05/01 09:09

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


0 0