矩阵分组[二分答案]

矩阵分组 
【描述】 
  有 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。没有别的分法,可以使答案更小。 
【测试数据】 
测试点  N,m范围 
1,2  n<=10,m<=10 
3-4  n=1,m<=2000 
5-7  n<=200,m<=200 
8-10  n<=2000,m<=2000 
所有权值1<=a_ij<=10^9 

 

分析

首先面对这种最大最小的问题,就应该思考一下二分答案,而这道题恰好也就用到了

接着分析一下题目中的分法要求,发现最后只会存在乘阶梯状下降或上升的部分(因为只能拐一次弯)

然后我们二分极差,重点就在于check函数了

如何来check呢?这是一个值得思考的问题,显然,一个部分不可能既包含整个矩阵的最大值又包含矩阵的最小值,这样肯定不是最优的情况,那么我们就可以依据这一点来进行check。

由于分出来的每个区域必定会占一个角,

如果我们从最大值所在的区域来看的话

可能会分别在不同的角上

而在不同的角判断的方法不一样

因为写好几种判断太麻烦了

所以直接存把矩阵旋转90.180.270度的情况一起存下来

相当于默认角在某一个位置,这样就可以用一种判断的方法就可以把所有情况都判断完了

--------------------- 本文来自 Faithfully_lyl 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/weixin_42557561/article/details/82925727?utm_source=copy


我的伪二分居然得了60分

没有得全的原因是我只考虑了两种情况(应该有4种)

竟然还有将矩阵翻转的操作,真是大开眼界

#include<bits/stdc++.h>
#define N 2005
#define inf 0x3fffffff
using namespace std;
int a[4][N][N];//四个方向
int n,m,Max=-inf,Min=inf;
int read(){
	int cnt=0;char ch=0;
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))cnt=cnt*10+(ch-'0'),ch=getchar();
	return cnt;
} 
bool check(int type,int x){
	if(type&1) swap(n,m);
	int limit[N],j;
	memset(limit,0,sizeof(limit));
	limit[0]=m;
	
	for(int i=1;i<=n;i++){ 
		for(j=1;j<=limit[i-1];j++)//单调递减 
			if(Max-a[type][i][j]>x) break;
		limit[i]=j-1;
	}
    //我们已经默认了最大值在左上角,最小值在右下角
    //判断4次是不会漏解的
    //这样将一个很难判断的大问题转换为了4个较为好判断的小问题
    //十分巧妙
	for(int i=1;i<=n;i++)
		for(int j=limit[i]+1;j<=m;j++){ 
			if(a[type][i][j]-Min>x){
				if(type&1) swap(n,m);
				return false;
			}
		}
	if(type&1) swap(n,m);
	return true;
}
bool pd(int x){
	for(int i=0;i<=3;i++) 
		if(check(i,x))return true;
	return false;
}
int main(){
	//freopen("1.in","r",stdin);
	n=read(),m=read();
	int x1=1,x2=1,x3=n,x4=m,tmp;
	int y1=1,y2=n,y3=m,y4=1;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			tmp=a[0][x1][y1++]=a[1][x2++][y2]=a[2][x3][y3--]=a[3][x4--][y4]=read();
			Max=max(Max,tmp),Min=min(Min,tmp);
		}
		x1++,y1=1; x2=1,y2--; x3--,y3=m; x4=m,y4++;
	} 
	int l=1,r=Max-Min;
	while(l<r){
		int mid=(l+r)>>1;
		if(pd(mid)) r=mid;
		else l=mid+1;
	}cout<<l;return 0;
} 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FSYo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值