矩阵分组
【描述】
有 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度的情况一起存下来
相当于默认角在某一个位置,这样就可以用一种判断的方法就可以把所有情况都判断完了
不过存储的写法很是巧妙啊,详细见代码
简直想去结交一下这篇代码的原码主
代码
#include<bits/stdc++.h>
#define P 19260817
#define ll long long
#define in read()
#define N 2009
using namespace std;
inline int read(){
char ch;int res=0;
while((ch=getchar())<'0'||ch>'9');
while(ch>='0'&&ch<='9'){
res=(res<<3)+(res<<1)+ch-'0';
ch=getchar();
}
return res;
}
int n,m,a[4][N][N],maxn=-1,minn=1000000009;
int end[N];
bool C(int u,int d){
if(u&1) swap(n,m);//由于旋转后行列会发生变化,我们就要顺带把n,m换一下
end[0]=m;
int i,j;
for(i=1;i<=n;++i){
for(j=1;j<=end[i-1];++j)
{
if(maxn-a[u][i][j]>d) break;
}
end[i]=j-1;
}//构造完包含最大值的区域,下面开始验证包含最小值的区域是否满足极差在d以内
for(int i=1;i<=n;++i){
for(int j=end[i]+1;j<=m;++j){
if(a[u][i][j]-minn>d){
if(u&1) swap(n,m);//还要换回来
return false;
}
}
}
if(u&1) swap(n,m);
return true;
}
bool check(int mid){
if(C(0,mid)) return true;//四个角落分别搞一遍
if(C(1,mid)) return true;
if(C(2,mid)) return true;
if(C(3,mid)) return true;
return false;
}
int main(){
n=in;m=in;int t;
int x=1,x1=1,x2=n,x3=m,y=1,y1=n,y2=m,y3=1;
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
t=a[0][x][y++]=a[1][x1++][y1]=a[2][x2][y2--]=a[3][x3--][y3]=in;//旋转后存储,这个东西,自己画下图,或者举几个例子就明白了
if(t>maxn) maxn=t;
if(t<minn) minn=t;
}
x++;y=1;
x1=1;y1--;
x2--;y2=m;
x3=m;y3++;
}
int l=0,r=maxn-minn,ans;
while(l<=r){//二分
int mid=l+r>>1;
if(check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
printf("%d",ans);
return 0;
}