本文涉及的基础知识点

C++算法:滑动窗口及双指针总结
C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频
本博文代码打包下载

[GESP202406 五级] 黑白格

题目描述

小杨有一个 n n n m m m 列的网格图,其中每个格子要么是白色,要么是黑色。

小杨想知道至少包含 k k k 个黑色格子的最小子矩形包含了多少个格子。

输入格式

第一行包含三个正整数 n , m , k n,m,k n,m,k,含义如题面所示。

之后 n n n 行,每行⼀个长度为 m m m 01 \texttt{01} 01 串,代表网格图第 i i i 行格子的颜色,如果为 0 \texttt{0} 0,则对应格子为白色,否则为黑色。

输出格式

输出一个整数,代表至少包含 k k k 个黑色格子的最小子矩形包含格子的数量,如果不存在则输出 0 0 0

样例 #1

样例输入 #1

4 5 5
00000
01111
00011
00011

样例输出 #1

6

提示

样例解释

对于样例 1 1 1,假设 ( i , j ) (i,j) (i,j) 代表第 i i i 行第 j j j 列,至少包含 5 5 5 个黑色格子的最小子矩形的四个顶点为 ( 2 , 4 ) (2,4) (2,4) ( 2 , 5 ) (2,5) (2,5) ( 4 , 4 ) (4,4) (4,4) ( 4 , 5 ) (4,5) (4,5),共包含 6 6 6 个格子。

数据范围

对于全部数据,保证有 1 ≤ n , m ≤ 100 1\le n,m\le 100 1n,m100 1 ≤ k ≤ n × m 1\le k\le n\times m 1kn×m

子任务编号 得分 n , m n,m n,m
1 1 1 20 20 20 ≤ 10 \le 10 10
2 2 2 40 40 40 n = 1 n=1 n=1 1 ≤ m ≤ 100 1\le m\le 100 1m100
3 3 3 40 40 40 ≤ 100 \le 100 100

Update on 2024/7/9:添加了若干组 hack 数据,感谢 @cff_0102 的贡献。

滑动窗口+ 前缀和

暴力做法是枚举起始位置和行列,时间复杂度是:O(nmnm),可能超时。
mat记录网格信息,mat[r][c]表示第r行c列是否是黑色格子。
枚举网格,第一层高度为height,第二层其实行行号r。
第三层枚举其实列号c,通过滑动窗口枚举结束列号c2。行在左闭右开空间[r,r+height),列在左闭半开的区间[c,c2)的黑色单格数大于等于k。c增加,c2增加或不变,绝对不会变小。
最小:height*(c2-c)就是答案。

代码

核心代码

#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>

#include <bitset>
using namespace std;



template<class T = int>
vector<T> Read(int n,const char* pFormat = "%d") {
	vector<T> ret(n);
	for(int i=0;i<n;i++) {
		scanf(pFormat, &ret[i]);	
	}
	return ret;
}

template<class T = int>
vector<T> Read( const char* pFormat = "%d") {
	int n;
	scanf("%d", &n);
	vector<T> ret;
	T d;
	while (n--) {
		scanf(pFormat, &d);
		ret.emplace_back(d);
	}
	return ret;
}

string ReadChar(int n) {
	string str;
	char ch;
	while (n--) {
		do
		{
			scanf("%c", &ch);
		} while (('\n' == ch));
			str += ch;
	}
	return str;
}
template<class T1,class T2>
void ReadTo(pair<T1, T2>& pr) {
	cin >> pr.first >> pr.second;
}

template<class INDEX_TYPE>
class CBinarySearch
{
public:
	CBinarySearch(INDEX_TYPE iMinIndex, INDEX_TYPE iMaxIndex) :m_iMin(iMinIndex), m_iMax(iMaxIndex) {}
	template<class _Pr>
	INDEX_TYPE FindFrist(_Pr pr)
	{
		auto left = m_iMin - 1;
		auto rightInclue = m_iMax;
		while (rightInclue - left > 1)
		{
			const auto mid = left + (rightInclue - left) / 2;
			if (pr(mid))
			{
				rightInclue = mid;
			}
			else
			{
				left = mid;
			}
		}
		return rightInclue;
	}
	template<class _Pr>
	INDEX_TYPE FindEnd(_Pr pr)
	{
		INDEX_TYPE leftInclude = m_iMin;
		INDEX_TYPE right = m_iMax + 1;
		while (right - leftInclude > 1)
		{
			const auto mid = leftInclude + (right - leftInclude) / 2;
			if (pr(mid))
			{
				leftInclude = mid;
			}
			else
			{
				right = mid;
			}
		}
		return leftInclude;
	}
protected:
	const INDEX_TYPE m_iMin, m_iMax;
};

template<class T = int>
class CPreSum2 {
public:
	template<class _Pr>
	CPreSum2(int rowCnt, int colCount, _Pr pr) :m_iRowCnt(rowCnt), m_iColCnt(colCount) {
		m_vSum.assign(rowCnt + 1, vector<int>(colCount + 1));
		for (int r = 0; r < rowCnt; r++) {
			for (int c = 0; c < colCount; c++) {
				m_vSum[r + 1][c + 1] = m_vSum[r][c + 1] + m_vSum[r + 1][c] - m_vSum[r][c] + pr(r, c);
			}
		}
	}
	T Get(int left, int top, int right, int bottom)const {
		return m_vSum[bottom + 1][right + 1] - m_vSum[top][right + 1] - m_vSum[bottom + 1][left] + m_vSum[top][left];
	}
	T GetTopLeft(int bottom, int right) { return m_vSum[bottom + 1][right + 1]; }
	T GetBottomRight(int top, int left) { return Get(left, top, m_iColCnt - 1, m_iRowCnt - 1); }
	vector<vector<T>> m_vSum;
	const int m_iRowCnt, m_iColCnt;
};
class Solution {
public:
	int Ans(vector<string>& mat, const int K) {
		const int R = mat.size(), C = mat[0].size();
		CPreSum2<int> ps(R, C, [&](int r, int c) {return mat[r][c] - '0'; });
		int ans = INT_MAX / 2;
		for (int height = 1; height <= R; height++) {
			for (int r = 0; r + height <= R; r++) {
				for (int c = 0, c2 = 1; c < C; c++) {
					while ((c2 < C) && (ps.Get(c, r, c2 - 1, r + height - 1) < K)) {
						c2++;
					}
					if (ps.Get(c, r, c2 - 1, r + height - 1) >= K) {
						ans = min(ans, height * (c2 - c));
					}
				}
			}
		}
		return (INT_MAX / 2 == ans) ? 0 : ans;
	}
};

int main() {
#ifdef _DEBUG
	freopen("a.in", "r", stdin);
#endif // DEBUG
	int R,C,K;
	cin >> R >> C >> K;
	vector<string> mat(R);
	for (int r = 0; r < R; r++) {
		cin >> mat[r];
	}
	auto res = Solution().Ans(mat,K);
#ifdef _DEBUG			
		//Out(mat, "mat=");
		//Out(b, ",b=");	
		//printf("K=%d", K);
#endif		
	cout << res << std::endl;		
	return 0;
}

单元测试

vector<string> mat;
		int K;
		TEST_METHOD(TestMethod11)
		{
			mat = { "00000","01111","00011","00011" },K = 5	;
			auto res = Solution().Ans(mat,K);
			AssertEx(6, res);
		}
		TEST_METHOD(TestMethod12)
		{
			mat = { "00000","01111","00011","00011" }, K = 50;
			auto res = Solution().Ans(mat, K);
			AssertEx(0, res);
		}

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐