【训练题69:二维前缀和 + 枚举优化】Portal | CF#745 Div1:A

题意

  • 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 a5,b4
      四个角随意
      四条边(扣掉四个角的位置)必须都是 1 1 1
      中间必须都是 0 0 0
  • 就是类似一个 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,m400

思路

  • 矩形,就是四条边。枚举四条边直接超时了,但是我们可以枚举三条边 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[D1][L] s [ D − 1 ] [ R ] s[D-1][R] s[D1][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[D1][L+1]s[D1][R1] 都必须为 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+14,因为高的限制 a ≥ 5 a\ge 5 a5
    然后多的这种情况也要考虑完毕。
  • 然后发现,我们对于每一个 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
*/

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值