【NOIP校内模拟】矩阵分组 matrix

【描述】
有 N 行 M 列的矩阵, 每个格子中有一个数字,现在需要你将格子的数字分为 A,B
两部分
要求:
1、每个数字恰好属于两部分的其中一个部分
2、每个部分内部方块之间,可以上下左右相互到达,且每个内部方块之间可以相互到达,
且最多拐一次弯
如:
AAAAA AAAAA AAAAA
AABAA BaAAA AAABB
ABBBA BBAAA AAABB
AABAA BaAAA ABBBB
AAAAA AAAAA BBBBB

(1) (2) (3)
其中(1)(2)是不允许的分法, (3)是允许的分法。在(2)中, a 属于 A 区域,这两个 a 元素之间
互相到达, 但是不满足只拐一次弯到达。
问: 对于所有合法的分组中, A 区域和 B 区域的极差,其中极差较大的一个区域最小值是
多少
提示: 极差就是区域内最大值减去最小值。
【输入】
第一行两个正整数 n,m
接下来 n 行,每行 m 个自然数 A_{i,j}表示权值
【输出】
输出一行表示答案
【输入样例】
4 4
1 12 6 11
11 4 2 14
10 1 9 20
4 17 13 10
【输出样例】
11
【样例解释】
1 12 6 11
11 4 2 14
10 1 9 20
4 17 13 10
分法不唯一,如图是一种合法的分法。左边部分极差 12-1=11,右边一块极差 20-10=10,
所以答案取这两个中较大者 11。没有别的分法,可以使答案更小。

很容易想到二分。

我们二分什么呢?二分极值

首先,A,B两部分一定是逐行长度递增(递减)的图形,才能满足最多拐一次弯到达,而且A,B可以互换

其次,最大值和最小值肯定不能在一个区域里,这样得出的答案肯定不是最优的,我们可以根据这个性质,找到check的方法

假设最大值在A这一部分,A覆盖了左上角,我们现在要先构造这么一个A,再来检验剩下B是否合法。

那么对于每一行,我们往右扫,直到找到一个x1 使得最大值x1>mid

那么对于剩下的B,如果有一个x1-最小值>mid,肯定不合法。

最后,A是可以在任意一个角的,这就需要将矩阵翻转,代码实现很简单。

#include<iostream>
#define N 2005
#define MAX 1000000005
using namespace std;
int n,m,maxv,minv=MAX;
int matrix[4][N][N];    //0 90 180 270
void INIT()
{
    ios::sync_with_stdio(false);
    cin.tie(NULL),cout.tie(NULL);
    cin>>n>>m;
    int x0=1,y0=1,x1=1,y1=n,x2=n,y2=m,x3=m,y3=1;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            int temp;
            cin>>temp;
            maxv=max(maxv,temp);
            minv=min(minv,temp);
            matrix[0][x0][y0]=matrix[1][x1][y1]=matrix[2][x2][y2]=matrix[3][x3][y3]=temp;
            y0++,x1++,y2--,x3--;
        }
        x0++;y0=1;
        x1=1;y1--;
        x2--;y2=m;
        x3=m;y3++; 
    }
}
int end[N];
bool BIGcheck(int state,int d)
{
    if(state&1) swap(n,m);

    //先构造包含最大值的区域 是一个逐行递减的图形 
    int i,j;
    end[0]=m;
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=end[i-1];j++)
        {
            if(maxv-matrix[state][i][j]>d)  break;          
        }
        end[i]=j-1;
    }
    
    //检验包含最小值的区域
    for(int i=1;i<=n;i++)
    {
        for(int j=end[i]+1;j<=m;j++)
        {
            if(matrix[state][i][j]-minv>d)
            {
                if(state&1) swap(n,m);//换回来 
                return false;
            }
        }
    } 
    if(state&1) swap(n,m);
    return true;
}
bool check(int d)
{
    //四个角落 
    if(BIGcheck(0,d))   return true;
    if(BIGcheck(1,d))   return true;
    if(BIGcheck(2,d))   return true;
    if(BIGcheck(3,d))   return true;
    return false; 
}
void SOLVE()
{
    //二分较大的极值 
    int l=0,r=maxv-minv;
    while(l<r)
    {
        int m=(l+r)>>1;
        if(check(m))    r=m;
        else l=m+1;
    }
    cout<<l;
}
int main()
{
    INIT();
    SOLVE();
    return 0;
}

转载于:https://www.cnblogs.com/Patrickpwq/articles/9738692.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值