<think>
题目要求:对于给定的t组测试数据,每组数据是一个n行m列的矩阵,表示队伍。每一列代表一个班级。我们需要对每个班级(每一列)按照身高从低到高排序(从小到大),但是注意:值为0的位置表示没有人,排序时0应该被视为一个特殊值,即0不用参与排序,但是0的位置在排序后仍然应该保持在原来的位置(即排序只对非0的元素进行,并且非0元素排序后,0的位置不变)。
思路:
对于每一列,我们单独处理:
1. 提取该列中所有非0的元素,并进行排序(从小到大)。
2. 然后,我们再次遍历该列,将非0的元素按顺序替换成排序后的结果,而0的位置保持不变。
注意:题目中说明了每个班级至少有一名学生,所以每一列至少有一个非0元素(不会全为0)。
由于数据规模:n*m的总和不超过3e6,所以我们可以对每一列单独提取非0元素,排序,然后重新放回去。这样每列的时间复杂度是O(n log n),总时间复杂度为O(m * n log n),而m和n最大都是1000,那么最坏情况下m=1000,n=1000,则总操作次数为1000 * (1000 log1000) ≈ 1000 * 1000 * 10 = 10e6,再加上总数据量n*m不超过3e6(注意题目说的是n×m的总和不超过3e6,也就是所有测试数据的总矩阵大小不超过3e6),所以这样处理是可行的。
具体步骤:
1. 读入t。
2. 对于每组数据:
读入n, m。
读入n行,每行m个整数。
创建一个新的矩阵(或者直接在原矩阵上修改),我们按列处理。
对于每一列j(0<=j<m):
创建一个临时数组temp,用于存放该列的非0元素。
遍历该列的所有行(i从0到n-1),如果当前元素不为0,则将其加入temp。
对temp进行排序(从小到大)。
然后再次遍历该列,对于该列的每一个位置,如果原位置不是0,则用排序后的下一个元素替换;如果是0,则保持不变。
例如样例中的第一组数据:
输入:
3 3
132 136 133
0 132 137
0 163 168
处理第一列:非0元素有[132],排序后还是[132];然后重新放回,第一列变为:132, 0, 0 -> 注意,0的位置不变。
处理第二列:非0元素有[136,132,163] -> 排序后:[132,136,163];然后重新放回:第一个元素132(替换原来的136)、第二个元素132(替换原来的132)?不对,这里要注意:我们重新放回的时候,应该按照原列的顺序,遇到非0的位置就放入排序后的元素,遇到0就跳过。
原第二列:[136,132,163] -> 非0元素提取出来为[136,132,163],排序后为[132,136,163]。
然后重新放回:第一行第二列原为136,现在改为132;第二行第二列原为132,现在改为136;第三行第二列原为163,现在改为163。所以第二列变为:132,136,163。
处理第三列:非0元素[133,137,168] -> 排序后[133,137,168],然后放回:第一行第三列133->133,第二行137->137,第三行168->168。
输出:
132 132 133
0 136 137
0 163 168
但是注意样例输出第一组是:
132 132 133
0 136 137
0 163 168
所以第二列的处理:原第二列是[136,132,163] -> 排序后的非0序列是[132,136,163]。
重新放回时:
第一行第二列:136 -> 替换为132(排序后序列的第一个)
第二行第二列:132 -> 替换为136(第二个)
第三行第二列:163 -> 替换为163(第三个)
因此,我们需要一个指针k,从0开始,指向排序后数组的当前元素。遍历该列每一行,如果原位置不是0,我们就将排序后的数组的第k个元素放在这里,然后k++;如果是0,则跳过(不改变,也不移动k)。
代码实现:
注意:题目要求输出时每个位置用空格分隔,每行末尾没有多余空格。
另外,样例第二组数据:
输入:
3 4
146 134 125 163
168 153 162 153
156 121 143 0
输出:
146 121 125 153
156 134 143 163
168 153 162 0
我们检查第二列(索引1):
原第二列:[134, 153, 121] -> 提取非0:[134,153,121] -> 排序:[121,134,153]
重新放回:
第一行第二列:134 -> 121
第二行第二列:153 -> 134
第三行第二列:121 -> 153
所以第二列变为:[121,134,153] -> 符合输出。
第三列(索引2):
原第三列:[125,162,143] -> 非0排序:[125,143,162]
重新放回:
第一行:125 -> 125
第二行:162 -> 143? 不对,输出第二行第三列是143吗?实际输出是:
第一行:125
第二行:143
第三行:162
但是原第三列第三行是143,输出第三行第三列是162(因为第三行第三列是143,重新放回时,第一行第三列:125(原125)->125;第二行第三列:162(原162)->143(这里错了?) 不对,应该是:
重新放回的过程:遍历该列每一行:
第一行:125 -> 非0,所以用排序后的下一个(第一个)125 -> 这里变成125(不变),k=1
第二行:162 -> 非0,用排序后的第二个元素143 -> 这里变成143,k=2
第三行:143 -> 非0,用排序后的第三个元素162 -> 这里变成162,k=3
所以第三列变为:125,143,162 -> 但是输出中第三列是:125,143,162,而输出样例第三列是:125,143,162(第一行125,第二行143,第三行162)?但是我们看样例输出第二行第三列是143,第三行第三列是162,所以是对的。
但是注意输出样例第二组:
146 121 125 153
156 134 143 163
168 153 162 0
第三列:第一行125,第二行143,第三行162 -> 符合。
第四列:
原第四列:[163,153,0] -> 非0元素[163,153] -> 排序后[153,163]
重新放回:
第一行:163 -> 153
第二行:153 -> 163
第三行:0 -> 不变(0)
所以第四列:153,163,0 -> 符合输出。
因此,我们按列处理即可。
代码步骤:
for (int group = 0; group < t; group++) {
读入n, m
初始化二维数组 grid[n][m]
读入n行数据
创建一个二维数组 result(或者直接修改grid,但注意放回的时候不能影响后续列的读取,因为我们是按列独立处理的,所以可以原地修改?但是注意,当我们处理某一列时,我们只读取该列原来的数据,然后修改该列。所以我们可以直接修改grid,因为处理每一列时,我们提取非0元素是从当前列中提取(即原数据),然后修改该列。所以不会影响其他列。
但是注意:如果我们按列处理,那么处理第一列时,我们修改了第一列的值,那么在处理第二列时,读取第二列的值会不会被第一列修改影响?不会,因为第二列的值并没有被修改(我们在处理第二列时,读取的是grid[i][j]中的j是第二列,而第一列已经被修改了,但是第二列还没有被修改,所以我们读取的是原来的第二列)。所以可以按列原地修改。
然而,我们重新放回的时候,会覆盖原来的值,但是我们在处理某一列时,已经将该列的所有原始数据提取出来了(在排序前已经提取),所以放回时修改该列不会影响其他列的处理。
所以,我们可以这样:
for (int j = 0; j < m; j++) {
vector<int> nonZero;
// 首先收集该列的非0元素
for (int i = 0; i < n; i++) {
if (grid[i][j] != 0) {
nonZero.push_back(grid[i][j]);
}
}
// 排序
sort(nonZero.begin(), nonZero.end());
// 然后重新放回该列
int idx = 0; // 指向nonZero的下标
for (int i = 0; i < n; i++) {
if (grid[i][j] != 0) {
grid[i][j] = nonZero[idx++];
}
// 否则,0保持不变,不进行任何操作
}
}
最后输出grid。
}
输出格式:每行m个数,每个数用空格隔开,行末无空格。
注意:题目要求输出时,每个整数后面跟一个空格,但是行末没有空格?样例输出每行末尾是有空格的?我们看样例输出:
132 132 133
0 136 137
0 163 168
实际上,样例输出每行末尾都有一个空格。但是题目没有明确要求,我们按照样例输出格式:每个数字后面跟一个空格,包括最后一个数字后面也有空格,然后换行。
但是,如果我们这样输出:
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cout << grid[i][j];
if (j < m-1) cout << ' ';
}
cout << endl;
}
这样输出的每行末尾没有空格,但是样例输出每行末尾有空格。
观察样例输出:每行最后一个数字后面也有一个空格,然后换行。所以我们可以这样:
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cout << grid[i][j] << ' ';
}
cout << endl;
}
但是这样每行末尾会多一个空格,题目是否接受?我们可以尝试,或者看样例输出要求。
但是题目描述输出格式是:“输出 一个 n 行 m 列的方阵”,没有具体说明空格,但是样例输出每行末尾都有空格。
为了保险,我们可以按照样例输出格式:每个数字后面跟一个空格,包括最后一个数字后面也有空格,然后换行。这样输出。
但是,在编程竞赛中,通常行末空格是允许的,只要格式一致即可。
我们按照样例输出格式,即每个数字后面都输出一个空格,然后换行。
但是注意:题目中有一组数据是0,我们输出0即可。
代码实现(C++):
</think>
为了解决这个问题,我们需要对给定的矩阵进行按列排序,其中每一列代表一个班级。排序要求是每个班级(列)的非零元素按照身高从小到大排序,而值为0的位置保持不变。以下是详细的解决方案和代码实现:
### 解决方案
1. **读取输入数据**:首先读取测试数据的组数 `t`,然后对于每组数据,读取矩阵的行数 `n` 和列数 `m`,接着读取矩阵的具体数据。
2. **处理每一列**:对于每一列,执行以下操作:
- **提取非零元素**:遍历该列的所有行,收集所有非零元素到一个临时数组中。
- **排序非零元素**:对临时数组中的非零元素进行升序排序。
- **重新填充列**:再次遍历该列,将排序后的非零元素依次放回原位置(跳过值为0的位置)。
3. **输出结果**:处理完所有列后,输出排序后的矩阵,每个元素后面跟一个空格,每行末尾换行。
### 代码实现
```cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n, m;
cin >> n >> m;
vector<vector<int>> grid(n, vector<int>(m));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> grid[i][j];
}
}
// 处理每一列
for (int j = 0; j < m; j++) {
vector<int> nonZero;
// 收集非零元素
for (int i = 0; i < n; i++) {
if (grid[i][j] != 0) {
nonZero.push_back(grid[i][j]);
}
}
// 排序非零元素
sort(nonZero.begin(), nonZero.end());
// 重新填充列
int idx = 0;
for (int i = 0; i < n; i++) {
if (grid[i][j] != 0) {
grid[i][j] = nonZero[idx++];
}
}
}
// 输出结果
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cout << grid[i][j] << ' ';
}
cout << '\n';
}
}
return 0;
}
```
### 代码解释
1. **输入处理**:
- 使用 `ios::sync_with_stdio(false)` 和 `cin.tie(nullptr)` 加速输入输出。
- 读取测试数据组数 `t`,然后依次处理每组数据。
- 读取矩阵的行数 `n` 和列数 `m`,并初始化一个 `n × m` 的矩阵 `grid`。
2. **按列排序**:
- 对于每一列 `j`,创建一个临时数组 `nonZero` 存储该列所有非零元素。
- 对 `nonZero` 进行升序排序。
- 再次遍历该列,将排序后的非零元素依次放回原位置(跳过值为0的位置)。
3. **输出结果**:
- 遍历排序后的矩阵,每个元素后面输出一个空格,每行末尾换行。
### 复杂度分析
- **时间复杂度**:对于每组数据,处理每列的时间复杂度为 O(n log n),其中 `n` 是行数。总时间复杂度为 O(t × m × n log n)。由于所有测试数据的 `n × m` 总和不超过 3×10⁶,因此该算法是高效的。
- **空间复杂度**:临时数组 `nonZero` 的空间复杂度为 O(n),总体空间复杂度为 O(n × m)。
### 相关问题