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