题目分析
我们需要在一个 n×nn \times nn×n 的网格中选择 kkk 个交点,使得:
- 每行恰好选择 kkk 个点
- 每列恰好选择 kkk 个点
- 任意四个被选中的点不能构成边平行于网格的矩形
这是一个经典的组合设计问题,可以通过有限射影平面的理论来解决。
数学背景
当 k−1k-1k−1 是素数时,存在一个优美的构造方法:
- 令 m=k−1m = k - 1m=k−1(mmm 为素数)
- 最小可能的 n=m2+m+1=k2−k+1n = m^2 + m + 1 = k^2 - k + 1n=m2+m+1=k2−k+1
- 这对应于有限射影平面的点数
构造方法
核心思路
将解构分为两部分:
111. 第一组行:所有通过"无穷远点"的线
222. 第二组行:仿射平面上的线加上对应的"无穷远点"
具体构造
对于 k=m+1k = m + 1k=m+1:
111. 第一组行(前 kkk 行):
- 每行都包含点 1(无穷远点)
- 其余点按块分配:i×m+j+1i \times m + j + 1i×m+j+1
222. 第二组行(后 m2m^2m2 行):
- 每行以 i+2i + 2i+2 开头(另一个无穷远点)
- 其余点由公式 (j+s×i) mod m+s×m+k+1(j + s \times i) \bmod m + s \times m + k + 1(j+s×i)modm+s×m+k+1 计算
- 这对应于斜率为 iii、截距为 jjj 的仿射线
参考代码
// No Rectangles
// UVa ID: 135
// Verdict: Accepted
// Submission Date: 2016-04-05
// UVa Run Time: 0.003s
//
// 版权所有(C)2016,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
int main(int argc, char *argv[]) {
cin.tie(0);
cout.sync_with_stdio(false);
int k;
bool printBlank = false; // 控制输出空行
// 读取多个k值
while (cin >> k) {
// 输出空行分隔不同的k(第一个k前不输出空行)
if (printBlank) cout << "\n";
else printBlank = true;
int m = k - 1; // m为素数(题目保证)
// 第一组行:所有通过"无穷远点1"的线
for (int i = 0; i < k; i++) {
// 每行第一个点固定为1(无穷远点)
cout << 1;
// 其余k-1个点按块分配
for (int j = 1; j < k; j++) cout << ' ' << i * m + j + 1;
cout << "\n";
}
// 第二组行:仿射平面上的线加上对应的无穷远点
for (int i = 0; i < m; i++) // i: 斜率
for (int j = 0; j < m; j++) // j: 截距
{
// 每行以i+2开头(另一个无穷远点)
cout << i + 2;
// 计算仿射线上的点
for (int s = 0; s < m; s++)
// 公式:(j + s*i) % m 确定块内偏移
// s*m + k + 1 确定块起始位置
cout << ' ' << (j + (s * i)) % m + s * m + k + 1;
cout << "\n";
}
}
return 0;
}
算法正确性证明
无矩形性质
在射影平面中:
- 任意两条直线恰好相交于一个点
- 不存在两条不同的直线包含两个相同的点
- 这就保证了不会出现四个点形成矩形
行列平衡
- 每行恰好 kkk 个点(射影平面中每条线包含 kkk 个点)
- 每列恰好 kkk 个点(射影平面中每个点位于 kkk 条线上)
复杂度分析
- 时间复杂度:O(k3)O(k^3)O(k3),但 k≤32k \leq 32k≤32,完全可行
- 空间复杂度:O(1)O(1)O(1),无需额外存储
示例验证
对于 k=2k = 2k=2(m=1m = 1m=1):
1 2
1 3
2 3
对于 k=3k = 3k=3(m=2m = 2m=2):
1 2 3
1 4 5
1 6 7
2 4 6
2 5 7
3 4 7
3 5 6
总结
本题利用有限射影平面的组合性质,通过巧妙的构造方法解决了无矩形点集问题。代码实现简洁高效,充分利用了数论和组合设计的理论成果。

133

被折叠的 条评论
为什么被折叠?



