2018.10.02【校内模拟】矩阵分组(二分)(贪心)(简单推理)

版权声明:转载请声明出处,谢谢配合。 https://blog.csdn.net/zxyoi_dreamer/article/details/82929020

描述

有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 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   \text{ } 11
11 4 2   \text{ } 14
10 1 9   \text{ } 20
4   \text{ } 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 i j a_ij <=10^9


解析:

首先看到题目知道是个二分。然而考场上由于并不知道怎么写check而挂掉了这道题。本来想写粒子群算法拿 60 p t s 60pts 暴力的,结果看到 T 3 T3 是一个可做的欧拉定理,就果断弃了 T 2 T2 的暴力。
现在想来,当时真是 (和谐) 了。

思路:

这个二分最难的就是怎么满足形状,找到形状的通性,这道题就解决了。
然而我再考场上莫名其妙考虑了环状。。。而环状根本不合法。

这道题唯一合法的是阶梯状。
证明也很显然。

那么我们就按照阶梯状来做,阶梯状的特点就是高度单调变化,单调不降或单调不升,那么我们只考虑单调不升的,其他的通过旋转矩阵来满足。代码中记录了四个矩阵,分别代表四个不同的旋转后得到的矩阵。

我们从第一列开始,尽可能地上升(假设当前区域包含了最大值,没包含没关系,我们可以通过旋转满足)。根据二分到的答案将能包含的全部包含进来,剩下的就是另一个区域的数,而假设最小值在那个区域,直接check,按照这样做的话,二分的上界就要在开始时设置成 m a x n m i n n maxn-minn

文章的最后有调试代码,可以输出读入的四个矩阵。


代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const

inline
ll getint(){
	re ll num;
	re char c;
	while(!isdigit(c=gc()));num=c^48;
	while(isdigit(c=gc()))num=(num<<1)+(num<<3)+(c^48);
	return num;
} 

cs int N=2005;
ll A[4][N][N];
int n,m;
ll maxn,minn=0x3f3f3f3f3f;
int endpos[N];

inline
bool check(int id,int k){
	if(id&1)swap(n,m);
	endpos[0]=m;
	for(int re i=1,tmp;i<=n;++i){
		for(tmp=1;tmp<=endpos[i-1];++tmp)
		if(maxn-A[id][i][tmp]>k)break;
		endpos[i]=tmp-1;
	}
	for(int re i=1;i<=n;++i){
		for(int re j=endpos[i]+1;j<=m;++j){
			if(A[id][i][j]-minn>k){
				if(id&1)swap(n,m);
				return false;
			}
		}
	}
	if(id&1)swap(n,m);
	return true;
}

inline
bool Check(int k){
	if(check(0,k))return true;
	if(check(1,k))return true;
	if(check(2,k))return true;
	if(check(3,k))return true;
	return false;
}

signed main(){
	n=getint();
	m=getint();
	for(int re i=1;i<=n;++i)
	for(int re j=1;j<=m;++j){
		A[0][i][j]=A[1][m-j+1][i]=A[2][n-i+1][m-j+1]=A[3][j][n-i+1]=getint();
		maxn=max(maxn,A[0][i][j]);
		minn=min(minn,A[0][i][j]);
	}
	int l=0,r=maxn-minn,ans=maxn-minn;
	while(l<r){
		int mid=(l+r)>>1;
		if(Check(mid))r=mid,ans=mid;
		else l=mid+1;
	}
	cout<<l;
	return 0;
}

以及这巨丑无比的调试代码:

	cerr<<1<<endl;
	for(int re i=1;i<=n;++i){
		for(int re j=1;j<=m;++j)
		cerr<<A[0][i][j]<<" ";
		cerr<<endl;
	}cerr<<endl<<2<<endl;
	for(int re i=1;i<=m;++i){
		for(int re j=1;j<=n;++j)
		cerr<<A[1][i][j]<<" ";
		cerr<<endl;
	}cerr<<endl<<3<<endl;
	for(int re i=1;i<=n;++i){
		for(int re j=1;j<=m;++j)
		cerr<<A[2][i][j]<<" ";
		cerr<<endl;
	}cerr<<endl<<4<<endl;
	for(int re i=1;i<=m;++i){
		for(int re j=1;j<=n;++j)
		cerr<<A[3][i][j]<<" ";
		cerr<<endl;
	}cerr<<endl;

猜你喜欢

转载自blog.csdn.net/zxyoi_dreamer/article/details/82929020