Problem
问题
Consider a grid such as the one shown. We wish to mark k intersections in each of n rows and n columns in such a way that no 4 of the selected intersections form a rectangle with sides parallel to the grid. Thus for k = 2 and n = 3, a possible solution is:
想象一个n行n列的网格,我们要在每行每列都标注k个交点,使得这些交点中的任意4个所构成的矩形(如果可以构成矩形),都不存在与网格线平行的边。比如k=2和n=3的情况,可能的解如下:
It can easily be shown that for any given value of k, k2-k+1 is a lower bound on the value of n, and it can be shown further that n need never be larger than this.
易知,对于任意的k,n的最小值为:k2-k+1。更进一步来讲,n也不会比这个值更大。
Write a program that will find a solution to this problem for k≤32, k-1 will be 0, 1 or prime.
写一个程序,计算所有k≤32的解。k-1可能是0,1或任何素数。
Input and Output
输入与输出
Input will consist of some values for k, one of each line. For each value of k, output will consist of n lines of k points indicating the selected points on that line. Print a blank line between two values of k.
输入包括一组k的值,每个一行。对每一个k值,要输出n行数,各行中的每个数代表在该行中选择的交点编号。每组k值的输出之间要有一个空行。
Sample input
输入示例
2
1
Sample output
输出示例
1 2
1 3
2 3
1
Analysis
分析
有趣的找规律题目,我想不会有人用递归的办法去暴力破解吧?一拿到题,我当时就懵了,想了好久没有理出头绪来。后来觉得光靠空想根本不可能解决问题,便找来尘封已久的棋盘来摆了一下,结果豁然开朗。强烈建议您先别急着看解析,想办法摆一下棋盘,或着在坐标纸上画一下。这是个锻炼思维的好机会,直接看解析就很没意思了。
摆的方法是非常直接的,第一行直接摆在前面,下面每行在不于上面的行冲突(构成矩形)的情况下,尽量往前摆就OK了。下图是k=5的情况:
仔细看看,应该发现规律了吧。还没有?再看下图:
从第6行开始,以下每4行为一组,每组编号用i表示,i=(0, 1, 2, 3);每组中的4行用j表示,j=(0, 1, 2, 3);每行中从第6列开始,右边每4列为一组,每组编号用s表示,s=(0, 1, 2, 3)。这里用s是为了避免和上面已经出现的k产生混淆。
(i, j, s)为一个三元组,现在要求的就是(0, 0, 0)到(3, 3, 3),一共4×4×4=64个值。i=0时,规律很明显,(0, j, s)=5 + s * (k - 1) + 1。式中第一个5是空出前面的5个格,s * (k - 1)计算当前4×4方块的起点,后面加1是为了保证数字从1开始,而不是0。
再看(1, 0, k)到(2, 0, k),第一个4×4方块(橙色)和上面的(黄色)排列是一样的,只是第二个方块(蓝色)比上面的(青色)向右移动了一格。由此可推知公式:
- (j + (s * i)) % m + s * m + k + 1
再用下面的两排验证此公式,都成功。现在才明白为什么n=k2-k+1,原来n=k+(k-1)(k-1),展开即得原式。这道题虽然解开了,但是这个公式我没有能够证明,这里面可能还蕴涵着更深奥的数理逻辑问题。如果您知其所以然,望不吝赐教!
Solution
解答
#include <iostream> using namespace std; //主函数 int main(void) { //循环处理每一组输入 for (int k, nFirst = 1; cin >> k; nFirst = 0) { //不是第一次输出时,前面要加空行 if (nFirst == 0) { cout << endl; } int m = k - 1; //输出头部的k行 for (int i = 0; i < k; ++i) { cout << 1; for (int j = 1; j < k; ++j) { cout << ' ' << i * m + j + 1; } cout << endl; } //以下算法请参照图示及相关文档 for (int i = 0; i < m; ++i) { for (int j = 0; j < m; ++j) { cout << i + 2; for (int s = 0; s < m; ++s) { cout << ' ' << (j + (s * i)) % m + s * m + k + 1; } cout << endl; } } } return 0; }
作者:王雨濛;新浪微博:@吉祥村码农;来源:《程序控》博客 -- http://www.cnblogs.com/devymex/ 此文章版权归作者所有(有特别声明的除外),转载必须注明作者及来源。您不能用于商业目的也不能修改原文内容。 |