【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;
}
posted @ 2018-10-02 22:22  Patrickpwq  阅读(327)  评论(0编辑  收藏  举报