A - Portal(cf) 前缀和 区间维护

本文介绍了一个字符数组问题的解决思路,通过枚举上下边界及利用前缀和来计算中间部分1的数量,最终求得改变数组四周及中间元素所需的最小操作次数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原题链接:Problem - 1580A - Codeforces

题意:给定一个字符数组,每个字符是0或者1。问能不能找到一个子数组n >= 5 && m >= 4,要使数组四周除了四个顶角其他都要变成1,中间的地方全要变成0,问最小改变次数。

分析一下,可以用for循环枚举上下边界,每次固定上下边界就开始看左右边界。

1.首先有个前缀和,s[i][j] = s[i - 1][j],每一列的前缀和,可以很好算中间部分的1的个数(需要转变为0的次数);

2.设此时固定了上下边界:先把每一列都当做中间部分,需要转换的次数加起来,相当于sum就是从1到j列的sum[j];遍历列的时候,cnt来计算这一列如果为右边界,要转换多少次。会发现,如果以j为右边界,i为左边界,答案其实是sum[j - 1] + cntj - sum[i] + cnti , 所以要cnti - sum[i]越小越好,所以要在遍历列的时候维护cnti - sum[i]的最小值,记在pre[]数组里;

3.这种题多思考,一般要取一个矩形的情况就可以先枚举上下边界,找到计算公式,维护该维护的列的区间。

4.还有一个要主要的地方,这个题直接把0/1存在int数组里好像不行,好像非得以char类型输入,然后变成int型才可以。

#include<iostream>
#include<cmath>
#include<vector>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
#define INF 0x3f3f3f3f
typedef pair<int, int> PII;
#define rep(i, m, n) for (int i = (m); i <= (n); ++i)
#define rrep(i, m, n) for (int i = (m); i >= (n); --i)
typedef long long ll;

const int N = 410;
int s[N][N], a[N][N];
int pre[N];
int n, m, p1, p0, cnt;

void solve() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++) {
			char c;
			cin >> c;
			a[i][j] = c - '0';
			s[i][j] = s[i - 1][j] + a[i][j];
		}
	int ans = INF;
	for (int i = 1; i <= n; i++)
		for (int j = i + 4; j <= n; j++) {
			int sum = 0;
			pre[0] = INF;
			for (int k = 1; k <= m; k++) {
				p1 = s[j - 1][k] - s[i][k];
				p0 = 2 - a[i][k] - a[j][k];
				cnt = j - i - 1 - p1;
				if (k > 3) ans = min(ans, pre[k - 3] + sum + cnt);
				sum += p1 + p0;
				pre[k] = min(pre[k - 1], cnt - sum);
			}
		}
	cout << ans << endl;
}

int main() {
	int t;
	for (scanf("%d", &t); t--; solve());
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值