P6497 Prosječni 题解
题目大意
一道构造题。
总结一下限制条件:
-
每行的平均数要在该行中出现;
-
每列的平均数要在该列中出现;
-
矩阵内的数字互不相同。
看一眼数据范围,发现
思路
首先可以发现的事情是:如果
下面是简要证明:
可以发现填完之后,每一行每一列都是一个 等差序列,并且是 奇数 项,那么这就保证了 该序列的平均数一定出现在该序列中。同时,由于每次都
接下来考虑 偶数。
从 奇数 的角度出发,但是如果直接填的话,虽然还是 等差序列,但是“平均数在序列中出现”这个限制就无法满足了。所以考虑进行一些调整,并且最终尽量缩小序列的值域(为避免超过
可以发现当
那么接下来考虑
先只考虑一个序列。如果直接填,可得
但这只是一个序列,后面还有好多个序列要填,怎么办?
考虑一个很经典的做法,将序列的每一项都乘上同一个数。
正确性还是很显然的,每一项都扩大一个相同的倍数,那么平均数也会扩大相同的倍数,并且肯定也还在序列里(如果读者还是不信,请手玩几个序列)。
同时还可以发现一个性质:将序列的每一项都加上同一个数。
每个数都加上相同的数,那么平均数也会加上相同的数,同时依然还在序列中(这不用再证明了吧)。
那么我们就可以将第一行作为 基底,第二行乘
那么我们可以想到,现在每一行已经满足题意了,那么就让每一行之间 相差的倍数 也满足条件即可。
这里提出了“相差的倍数”这么一个东西,是什么意思呢?
举个例子:现在给出一个合法的序列:
那么我们就让每一行之间相差的数也是一个合法的 差 的序列就可以了。
那么我们可以考虑填完第一行之后填第一列,然后根据每一行第一列的数,填完该行剩下的数。
那么整体上,我们要求每一行,小的数在左,大的数在右;每一列,小的数在上,大的数在下。
现在考虑当
首先第一行前面已经讨论过:
由于数字不可重复,所以第一列中除了
1 2 3 6
7
13
31
再填第二行(同理,也可以填第二列),
第三行:
第四行:
于是得到了:
1 2 3 6
7 8 9 12
13 14 15 18
31 32 33 36
(最终输出时是不用管缩进的,此处只是为了看起来整齐)
同时我们可以发现,每一列中的某个数都是上面那个数再加上 差的序列 的对应的那一位再乘上第一行最后一个数那么多倍。
本来第一行中
举几个例子:
那么这样我们还可以省掉单独填第一列的操作,直接根据第一行填剩下的所有行即可。
这就是一个合法的矩阵,但可能还是会有人质疑为什么这样就一定不会重复。下面简单证明一下(可能说得有些模糊,得感性理解)。
首先每一行填的方法肯定是合法的,那么最右边的数就是这一行的最大值,而下一行的第一个数(也就是在填第一列时已经确定的数)是已经规定要比“最大值”大的。举个例子:第一行为
但是我们可以注意到一件事,由于
下面考虑
还是先看第一行,我们还是以中间点右边的的那个数作为平均数,即
接下来说明如何进行 均摊。
首先由于不能有相同的数,所以直接分成
还是举上面的例子,负方向的贡献分别是
那么推广一下,正方向的贡献就应该是从
不过当
现在算一下最大值会不会超过
奇数的情况前面已经算过了,现在只考虑偶数。那么最大的偶数便是
实现
首先特判掉奇数和
差分数组 的好处其实就是大部分相同,操作起来方便。比如当
#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n;
int mp[N][N]; // 填充之后的二维数组
int cha[N]; // 差分数组
signed main() {
cin >> n;
if (n == 2) return puts("-1"), 0; // 特判 n = 2 的情况
if (n & 1) { // n 为奇数
int idx = 0;
for (int i = 1; i <= n; i ++ ) {
for (int j = 1; j <= n; j ++ )
cout << ++ idx << ' '; // 每次 +1,依次输出即可
puts("");
}
return 0;
}
for (int i = 2; i <= n; i ++ ) cha[i] = 1; // 都初始成 1
if (n == 4) {
cha[n] = 3; // n = 4 的特判
} else { // 对两个位置进行更改
int mid = (n >> 1) + 2; // 虽然变量名为 mid,但并不是最中间,而是中间靠右那个数的下一位
cha[mid] ++, cha[n] ++ ; // 变成 2
}
mp[1][1] = 1;
for (int i = 2; i <= n; i ++ ) mp[1][i] = mp[1][i - 1] + cha[i]; // 填第一行
for (int i = 2; i <= n; i ++ ) { // 枚举行
for (int j = 1; j <= n; j ++ ) { // 枚举列
mp[i][j] = mp[i - 1][j] + cha[i] * mp[1][n]; // 根据第一行填下面的所有行
}
}
for (int i = 1; i <= n; i ++ ) {
for (int j = 1; j <= n; j ++ )
cout << mp[i][j] << ' '; // 输出
puts("");
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现