P6497 Prosječni 题解

题面

题目大意

一道构造题。

总结一下限制条件:

  1. 每行的平均数要在该行中出现;

  2. 每列的平均数要在该列中出现;

  3. 矩阵内的数字互不相同。

看一眼数据范围,发现 n 的范围只是 [1,100],而矩阵中的数不大于 109 即可,所以操作空间还是很大的。

思路

首先可以发现的事情是:如果 n奇数 的话,直接从上到下,从左到右,从 1 开始,每次 +1,直接填即可(参考样例 1)。并且,如果 n 为奇数,那么最大为 99,而根据此方法,最后一个数最大,是 9801,并没有超过 109

下面是简要证明:

可以发现填完之后,每一行每一列都是一个 等差序列,并且是 奇数 项,那么这就保证了 该序列的平均数一定出现在该序列中。同时,由于每次都 +1,所以矩阵中也不会有相同的数。

接下来考虑 偶数

奇数 的角度出发,但是如果直接填的话,虽然还是 等差序列,但是“平均数在序列中出现”这个限制就无法满足了。所以考虑进行一些调整,并且最终尽量缩小序列的值域(为避免超过 109)。

可以发现当 n=2 时是 无解 的,因为任意一行,任意一列都只有两个数,如果要使 “平均值出现在序列中” 的话,那么只有在 这两个数相等 的时候才能成立,可是题目又要求不能有相同的数字,所以 无解(换句话说就是根本没有调整的空间)。

那么接下来考虑 n=4 的情况。

先只考虑一个序列。如果直接填,可得 1,2,3,4,很明显,平均数不在序列中,于是进行微调。由于是“微调”,所以尽量让平均数还是在靠近中间的位置。那么我们钦定平均数为 3(可能有人会问为什么不是 2,原因是如果选了 2,那么比平均数小的就只有一个 1,对平均数负方向的贡献就只有 21=1,而 2 的后面还有两个数,无法平衡),那么 12 对平均数负方向的贡献分别为 21,那么第 4 个数对平均值正方向的贡献就必须是 1+2=3,所以第 4 个数就应该是 6。于是便得到了如下序列:1,2,3,6

但这只是一个序列,后面还有好多个序列要填,怎么办?

考虑一个很经典的做法,将序列的每一项都乘上同一个数

正确性还是很显然的,每一项都扩大一个相同的倍数,那么平均数也会扩大相同的倍数,并且肯定也还在序列里(如果读者还是不信,请手玩几个序列)。

同时还可以发现一个性质:将序列的每一项都加上同一个数

每个数都加上相同的数,那么平均数也会加上相同的数,同时依然还在序列中(这不用再证明了吧)。

那么我们就可以将第一行作为 基底,第二行乘 2 倍,第三行乘 3 倍,以此类推。然后我们就会发现,虽然每一行满足条件了,但是每一列却变成了等差数列(公差为第一行对应的那一列的那个数),并且是偶数项,是不满足题意的。

那么我们可以想到,现在每一行已经满足题意了,那么就让每一行之间 相差的倍数 也满足条件即可。

这里提出了“相差的倍数”这么一个东西,是什么意思呢?

举个例子:现在给出一个合法的序列:1,2,3,6。计算相邻两项的差,得到 1,1,3。如果将 的序列乘上 2 倍,再得到的新序列便是 1,3,5,11,可以发现该序列依然合法,并且平均数还是在第 3 个位置。

那么我们就让每一行之间相差的数也是一个合法的 的序列就可以了。

那么我们可以考虑填完第一行之后填第一列,然后根据每一行第一列的数,填完该行剩下的数。

那么整体上,我们要求每一行,小的数在左,大的数在右;每一列,小的数在上,大的数在下。

现在考虑当 n=4 时如何填。

首先第一行前面已经讨论过:1,2,3,6

由于数字不可重复,所以第一列中除了 1,不能再有 2,3,6,那么为了方便,此处可以暴力一点,直接取 7(即为 6+1),也就是 1,7,13,31。如下:

 1  2  3  6
 7
13
31

再填第二行(同理,也可以填第二列),7,8,9,12(鉴于在第一列,我们已经控制好了每行之间的 ,所以对于每一行,类比于第一行,不用乘倍数,直接按照原始的 差的序列 填即可)。

第三行:13,14,15,18

第四行:31,32,33,36

于是得到了:

 1  2  3  6
 7  8  9 12
13 14 15 18
31 32 33 36

(最终输出时是不用管缩进的,此处只是为了看起来整齐)

同时我们可以发现,每一列中的某个数都是上面那个数再加上 差的序列 的对应的那一位再乘上第一行最后一个数那么多倍。

本来第一行中 12 之间差 1,但是最后一项是 6,于是第一列中 17 之间就差了 6,相当于是将 差的序列 扩大了 6 倍。

举几个例子:8=2+1×6,15=9+1×6,36=18+3×6

那么这样我们还可以省掉单独填第一列的操作,直接根据第一行填剩下的所有行即可。

这就是一个合法的矩阵,但可能还是会有人质疑为什么这样就一定不会重复。下面简单证明一下(可能说得有些模糊,得感性理解)。

首先每一行填的方法肯定是合法的,那么最右边的数就是这一行的最大值,而下一行的第一个数(也就是在填第一列时已经确定的数)是已经规定要比“最大值”大的。举个例子:第一行为 1,2,3,6,我们规定第二行第一个数是 7,同时第二行是 7,8,9,12,而我们第一行进行了缩放,所以第三行就刚好是 13,也是大于 12 的。所以,这样填的方法一定是合法的(毕竟我也 AC 了啊)。

但是我们可以注意到一件事,由于 n=4,同时钦定第 3 个数为平均数,那么前两个数对平均数负方向的贡献全部由第 4 个数来承担了,那如果后面不只有一个数呢?也就是 n 大于 4,并且是偶数的情况。

下面考虑 n=6 的情况。

还是先看第一行,我们还是以中间点右边的的那个数作为平均数,即 1,2,3,4,5,6 中的 4。那么前三个数对平均数负方向的贡献就是 3+2+1=6,要分给后面两个数承担。但是我们希望最后一个数(也就是最大的数)尽量小,这样才更不会超出 109。那么就尽量 均摊

接下来说明如何进行 均摊

首先由于不能有相同的数,所以直接分成 3+3 是不行的,那么就还是要拆成一个序列。

还是举上面的例子,负方向的贡献分别是 3,2,1,但是正方向却只有两个数,那很明显,最好的方法就是将 1 加到最大的数上面去,变成 2,4(如果加到 2 上面,就又变成 3,3 了)。

那么推广一下,正方向的贡献就应该是从 2 开始,后面有一段,每次 +1,之后还有一个 +2 的(因为把 1 加上去了)。比如当 n=10,负方向为 5,4,3,2,1,那么正方向就为 1,2,3,4,52,3,4,6

不过当 n=4 时是特殊的,只有最后一个数是正方向 +3

现在算一下最大值会不会超过 109

奇数的情况前面已经算过了,现在只考虑偶数。那么最大的偶数便是 100。第一行中选定的平均数为 51,那么最后一列第一个数就是 102。那么最后一行最后一列的数就是 102+(50+49+48++1+2+3++49+51)×102=260202<109

实现

首先特判掉奇数和 n=2 的情况,然后引入 差分数组,并进行一些微调,最后再根据发现的规律递推填充即可。

差分数组 的好处其实就是大部分相同,操作起来方便。比如当 n=8 时,那么 差分数组 就应该是 1,1,1,1,2,1,2。只有两个位置是 2,其余都是 1(当然要特判 n=4)。

Code

#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;
}
posted @   Ifyoung  阅读(40)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示