题意
Q老师 得到一张 n 行 m 列的网格图,上面每一个格子要么是白色的要么是黑色的。
Q老师认为失去了 十字叉 的网格图莫得灵魂. 一个十字叉可以用一个数对 x 和 y 来表示, 其中 1 ≤ x ≤ n 并且 1 ≤ y ≤ m, 满足在第 x 行中的所有格子以及在第 y 列的 所有格子都是黑色的
例如下面这5个网格图里都包含十字叉
第四个图有四个十字叉,分别在 (1, 3), (1, 5), (3, 3) 和 (3, 5).
下面的图里没有十字叉
Q老师 得到了一桶黑颜料,他想为这个网格图注入灵魂。 Q老师 每分钟可以选择一个白色的格子并且把它涂黑。现在他想知道要完成这个工作,最少需要几分钟?
Input
第一行包含一个整数 q (1 ≤ q ≤ 5 * 10^4) — 表示测试组数
对于每组数据:
第一行有两个整数 n 和 m (1 ≤ n, m ≤ 5 * 10^4, n * m ≤ 4 * 10^5) — 表示网格图的行数和列数
接下来的 n 行中每一行包含 m 个字符 — ‘.’ 表示这个格子是白色的, ‘*’ 表示这个格子是黑色的
保证 q 组数据中 n 的总和不超过 5 * 10^4, n*m 的总和不超过 4 * 10^5
Output
答案输出 q 行, 第 i 行包含一个整数 — 表示第 i 组数据的答案
输入样例
9
5 5
..*..
..*..
*****
..*..
..*..
3 4
****
.*..
.*..
4 3
***
*..
*..
*..
5 5
*****
*.*.*
*****
..*.*
..***
1 4
****
5 5
.....
..*..
.***.
..*..
.....
5 3
...
.*.
.*.
***
.*.
3 3
.*.
*.*
.*.
4 4
*.**
....
*.**
*.**
输出样例
0
0
0
0
0
4
1
1
2
提示
分析
- 题目分析
这道题就是找到一个网格图和含十字叉网格图之间差的黑格子数。
十字叉的要求是任意一行全为黑格,任意一列也全为黑格。其中“任意”这两个限定词就大大降低了难度。所以只要找到含黑格数最多的一行和一列,再将它们所构成的十字叉结构中所差的黑格数输出即可。
问题在于,这样的做法存在一个误差。
假如有一个以上的列都是最大值,而记录的最大列与最大行的交叉处为黑色,而其他的最大列中存在至少一个列与最大行交叉处为空白,这就代表记录的最大列和最大行得出的答案并不是最小。因为交叉处为空白的最大列显然需要填补的格子会少一个。
这个其实也可以实现判断。在结束统计后,遍历记录的行数组和列数组,列出行和列中所有的最大值。再遍历这些最大值,找到一对交叉处为空格的行和列即可。若都没有交叉处则答案就是任意一对最大行和列对应的答案。
不过我后来改成了直接遍历行数组和列数组,将所有行和列对应的答案求出找最小。由于比较懒【orz】且这个修改思路不一定能写出来,所以就没有按着之前的直接锁定最大值的思路来完善代码了。
总结
- 又是被玄幻状态困扰的lazy孩子
代码
//
// main.cpp
// lab2
//
//
#include <iostream>
#include <string.h>
#include <vector>
#include <algorithm>
#include <limits.h>
using namespace std;
int row[50001];
int col[50001];
vector<int> black[50001];
int main()
{
ios::sync_with_stdio(false);
char c;
int q = 0,n = 0,m = 0,max_row = 0,max_col = 0,ans = INT_MAX;
cin>>q;
while( q-- )
{
cin>>n>>m;
ans = INT_MAX;
max_col = 0;
max_row = 0;
memset(row, 0, sizeof(row));
memset(col, 0, sizeof(col));
for( int i = 0 ; i < n ; i++ )
black[i].clear();
for( int i = 0 ; i < n ; i++ )
{
for( int j = 0 ; j < m ; j++ )
{
cin>>c;
if( c != '.' )
{
row[i]++; //记录每一行和每一列中的黑色格子数量
col[j]++;
black[i].push_back(j); //记录每一行里格子为黑色的列序号
// if( col[j] > col[max_col] )
// max_col = j;
}
}
// if( row[i] > row[max_row] )
// max_row = i;
}
//直接在记录过程中记录黑色格子数最大的行和列
//这种做法是正确的,但在计算机实现的过程中存在误差
//也就是假如有一个以上的列都是最大值,而记录的最大列与最大行的交叉处为黑色,而其他的最大列中存在至少一个列与最大行交叉处为空白,这就代表记录的最大列和最大行得出的答案并不是最小。因为交叉处为空白的最大列显然需要填补的格子会少一个
//这个其实也可以实现判断。但是在输入过程中判定不好实现,而结束之后循环和下面的做法其实差别不大。
// ans = m - row[max_row] + n - col[max_col];
int sum = 0;
for( int i = 0 ; i < n ; i++ ) //遍历记录的行和列数组
{
for( int j = 0 ; j < m ; j++ )
{
sum = m + n - ( row[i] + col[j] ); //得到第i行和第j列需要填补的格子数量
//如果交叉处为空白,则应该减少一个填补格子
if( find(black[i].begin(), black[i].end(), j) == black[i].end() )
sum--;
if( sum == 0 ) //如果为0,说明已找到一个十字叉,该网格图不需要再填补
{
ans = 0;
break;
}
ans = min(ans, sum); //取最小值
}
}
cout<<ans<<endl;
}
return 0;
}