题意
- Portal | CF#745 Div1:A
给定一个 n × m n\times m n×m 的 01 01 01 矩阵
若一个矩形长度为 a × b a\times b a×b,其中 a a a 是 r o w row row, b b b 是 c o l u m n column column,是一个传送门:-
a
≥
5
,
b
≥
4
a\ge 5,b\ge 4
a≥5,b≥4
四个角随意
四条边(扣掉四个角的位置)必须都是 1 1 1
中间必须都是 0 0 0
-
a
≥
5
,
b
≥
4
a\ge 5,b\ge 4
a≥5,b≥4
- 就是类似一个
M
i
n
e
c
r
a
f
t
Minecraft
Minecraft 的传送门
一次操作可以把一个 0 , 1 0,1 0,1 翻转。
问你最少操作次数,使得一个子矩阵是传送门 - n , m ≤ 400 n,m\le 400 n,m≤400
思路
- 矩形,就是四条边。枚举四条边直接超时了,但是我们可以枚举三条边
O
(
n
3
)
O(n^3)
O(n3) 貌似挺
O
K
OK
OK 的。
假设我们枚举了 y = L , y = R , x = D y=L,y=R,x=D y=L,y=R,x=D 的三条直线。
我们现在要找到最优的直线 x = U x=U x=U,让这个四条直线围起来作为传送门的四条边,满足操作次数最少。 - 假设我们已知
x
=
D
x=D
x=D 的答案,最少操作次数为
t
m
p
tmp
tmp
我们每次直线 x = D x=D x=D 向下移动到 x = D + 1 x=D+1 x=D+1,此时答案从原来的 t m p tmp tmp 怎么更新了呢?
首先,竖着的边一定是多两个需要考虑的点,即 s [ D − 1 ] [ L ] s[D-1][L] s[D−1][L] 和 s [ D − 1 ] [ R ] s[D-1][R] s[D−1][R],必须为 1 1 1
然后就是多出来中间的一条,即 s [ D − 1 ] [ L + 1 ] ∼ s [ D − 1 ] [ R − 1 ] s[D-1][L+1]\sim s[D-1][R-1] s[D−1][L+1]∼s[D−1][R−1] 都必须为 0 0 0
然后发现,不管之前的 t m p tmp tmp 怎么样,这两坨要求都是必须要满足的,也就是 t m p tmp tmp 一定会多出来一些操作,让这两条满足。 - 然后考虑,因为
x
=
D
+
1
x=D+1
x=D+1,此时,我们最高的能加进来
x
=
D
+
1
−
4
x=D+1-4
x=D+1−4,因为高的限制
a
≥
5
a\ge 5
a≥5
然后多的这种情况也要考虑完毕。 - 然后发现,我们对于每一个
x
=
D
x=D
x=D,我们不要把
t
m
p
tmp
tmp 考虑下面这条
s
[
D
]
[
L
]
∼
s
[
D
]
[
R
]
s[D][L]\sim s[D][R]
s[D][L]∼s[D][R],因为不然会很难保存与转移。
然后就差不多答案能出来了…
算一个子矩阵的操作数的话,用二维前缀和算出矩阵内的 1 1 1 的个数即可。
代码
- 时间复杂度: O ( n 3 ) O(n^3) O(n3)
#include<bits/stdc++.h>
using namespace std;
void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x << " ] , ";show(args...);}
typedef long long ll;
const int MAX = 405;
const int MOD = 1e9+7;
char ss[MAX][MAX];
int num[MAX][MAX];
int val(int x1,int y1,int x2,int y2){ // 从左上角 [x1,y1] 到右下角 [x2,y2] 的子矩阵中有多少个1
return num[x2][y2] - num[x1-1][y2] - num[x2][y1-1] + num[x1-1][y1-1];
}
int main()
{
int T;scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;++i)
scanf("%s",ss[i]+1);
for(int i = 1;i <= n;++i){
for(int j = 1;j <= m;++j){
num[i][j] = num[i-1][j] + num[i][j-1] - num[i-1][j-1];
if(ss[i][j] == '1')num[i][j]++;
}
}
int ans = n * m; // 初始化一个很大的值即可
for(int L = 1;L <= m;++L)
for(int R = L + 3;R <= m;++R){ // 注意宽度的要求
int tmp = n * m;
for(int D = 5;D <= n;++D){ // 注意高度的要求
if(ss[D-1][L] == '0')tmp++; // 两边两个
if(ss[D-1][R] == '0')tmp++;
tmp += val(D-1,L+1,D-1,R-1); // 中间空的一条
int now = (R - L - 1) - val(D-4,L+1,D-4,R-1) + 3 - val(D-3,L,D-1,L) + 3 - val(D-3,R,D-1,R) + val(D-3,L+1,D-1,R-1);
tmp = min(tmp,now); // 多一种,x=D-4 的选法
ans = min(ans,tmp + ((R - L - 1) - val(D,L+1,D,R-1))); // 还要加上最下面那条边
}
}
printf("%d\n",ans);
}
return 0;
}
/**
001010001
101110100
000010011
100000001
101010101
110001111
000001111
111100000
000110000
*/