P7741 [AHOI2007] 石块地板
FBI Warning
此题是一道思维题,请不要"对着题解调试程序"(大雾
前置芝士
一维前缀和(就够了)
最大子段和(P1115)
二维数组(能做到蓝题的应该都会吧)
思路
注意,这题的 $n$ 和 $m$ 是和其他题反过来的!
这题明显是一个卡了我两天的最大子矩阵和板子题。
有同学就要问了:那为什么是“和”呢?问的是“差最大”啊?
但是这个“差最大”问题也可以转换成“和最大”问题。
黑(的数目) - 白(的数目)最大,即要求黑尽可能多,白尽可能少。
令黑色表示 $1$,白色表示 $-1$,即“$1$ 尽可能多,$-1$ 尽可能少”。
$1$ 尽可能多,所有 $1$ 和 $-1$ 的和就尽可能大。
$-1$ 尽可能少,所有 $1$ 和 $-1$ 的和也尽可能大。
反过来,只要所有 $1$ 和 $-1$ 的和尽可能大,$1$ 就多,$-1$ 就少,(数目的)差也就大。
所以保证矩阵内的 $1$ 和 $-1$ 的和尽可能大即可,差即为 $1$ 和 $-1$ 的和。
是不是恍然大明白?(我猜你没有
那么怎么找出这个“和最大”的子矩阵呢?枚举,但不是直接暴力。
首先,在矩阵上划定一个范围:
枚举这个范围需要 $n^2$ 的复杂度,子矩阵将在这个范围中确定。
将这个范围中每行的和算出来:
$s_i$ 表示第 $i$ 行每个数的和,灰框是一个临时的数组。
每行的和需要 $nm$ 的复杂度,但可以用前缀和优化成 $O(m)$。
那么,这个范围就被抽象成了灰框内的数组。
算这个数组的最大子段和,最大的子段就对应着最大的矩阵。
这样,最大矩阵就找到了,复杂度 $O(n^2m)$。
代码
#include <iostream>
#define qjh(r, x, y) s[r][y] - s[r][x - 1]
using namespace std;
int s[401][401], t[401], m, n, ans;char c;
void get()
{
for(int i = 1;i <= m;++i)
{
t[i] = max(t[i], t[i] + t[i - 1]);
ans = max(ans, t[i]);
}
}
int main()
{
cin >> m >> n;
for(int i = 1;i <= m;++i)
for(int j = 1;j <= n;++j)
{
cin >> c;
if(c == '1') s[i][j] = s[i][j - 1] + 1;
if(c == '0') s[i][j] = s[i][j - 1] - 1;
}
for(int i = 1;i <= n;++i)
for(int j = i;j <= n;++j)
{
for(int k = 1;k <= m;++k)
t[k] = qjh(k, i, j);
get();
}
cout << ans;
return 0;
}