描述
有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个自然数
表示权值
输出
输出一行表示答案
样例输入
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<= <=10^9
解析:
首先看到题目知道是个二分。然而考场上由于并不知道怎么写check而挂掉了这道题。本来想写粒子群算法拿
暴力的,结果看到
是一个可做的欧拉定理,就果断弃了
的暴力。
现在想来,当时真是 (和谐) 了。
思路:
这个二分最难的就是怎么满足形状,找到形状的通性,这道题就解决了。
然而我再考场上莫名其妙考虑了环状。。。而环状根本不合法。
这道题唯一合法的是阶梯状。
证明也很显然。
那么我们就按照阶梯状来做,阶梯状的特点就是高度单调变化,单调不降或单调不升,那么我们只考虑单调不升的,其他的通过旋转矩阵来满足。代码中记录了四个矩阵,分别代表四个不同的旋转后得到的矩阵。
我们从第一列开始,尽可能地上升(假设当前区域包含了最大值,没包含没关系,我们可以通过旋转满足)。根据二分到的答案将能包含的全部包含进来,剩下的就是另一个区域的数,而假设最小值在那个区域,直接check,按照这样做的话,二分的上界就要在开始时设置成 。
文章的最后有调试代码,可以输出读入的四个矩阵。
代码:
#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;