1. 程式人生 > >【2018/10/02測試T2】【WOJ 4019】矩陣分組

【2018/10/02測試T2】【WOJ 4019】矩陣分組

【題目】

題目描述:

有 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,6,7 n ≤ 200,m ≤ 200
8,9,10 n ≤ 2000,m ≤ 2000

所有權值 1 ≤ a_{i,j}  ≤ 10^9

【分析】

看到“極差較大的一個區域最小值是多少”這樣的字眼的時候其實就知道這道題多半是二分

但是想到二分之後,check 中間值貌似又無從下手

於是在考場上打了個 20 分暴力就沒去管它了

話不多說,開始講正解,正解做法也就是二分,不過這個 check 比較巧妙

很容易想到的是,對於一個合法的分法,A 和 B 應該是呈階梯狀

設 A 分佈在左上角,將矩陣旋轉三次就可以得到所有的情況,因此寫一個 check 函式就行

設最大值在 A 區域,那麼我們從第一行開始找到第一個與最大值差值不在大於 mid 的位置 p 

在第二行不超過 p-1 的位置同樣找一個這樣的邊界, 以下的所有行都這樣弄

這樣子我們就將矩陣劃分為了兩個區域,我們枚舉出來的左邊區域一定滿足條件,那麼我們再判斷右區域滿不滿足(與最小值的差值在條件範圍內,也即都小於等於 mid

把旋轉的四種情況都判斷一下就可以得出答案

【程式碼】

#include<cctype>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 2005
using namespace std;
int Read()
{
	int x=0;
	char c=getchar();
	while(!isdigit(c))
	  c=getchar();
	while(isdigit(c))
	{
		x=(x<<1)+(x<<3)+(c^'0');
		c=getchar();
	}
	return x;
}
int n,m,maxn,minn;
int rec[N],a[10][N][N];
bool Check(int id,int mid)
{
	int i,j;
	if(!(id&1))
	  swap(n,m);
	rec[0]=m;
	for(i=1;i<=n;++i)
	{
		for(j=1;j<=rec[i-1];++j)
		  if(maxn-a[id][i][j]>mid)
		    break;
		rec[i]=j-1;
	}
	for(i=1;i<=n;++i)
	  for(j=rec[i]+1;j<=m;++j)
	    if(a[id][i][j]-minn>mid)
	    {
	    	if(!(id&1))
			  swap(n,m);
	    	return false;
	    }
	if(!(id&1))
	  swap(n,m);
	return true;
}
bool check(int mid)
{
	if(Check(1,mid))  return true;
	if(Check(2,mid))  return true;
	if(Check(3,mid))  return true;
	if(Check(4,mid))  return true;
	return false;
}
int main()
{
//	freopen("matrix.in","r",stdin);
//	freopen("matrix.out","w",stdout);
	int i,j,k;
	maxn=0,minn=1e+9;
	n=Read(),m=Read();
	int x1=1,y1=1,x2=1,y2=n,x3=n,y3=m,x4=m,y4=1;
	for(i=1;i<=n;++i)
	{
		for(j=1;j<=m;++j)
		{
			k=Read();
			maxn=max(maxn,k);
			minn=min(minn,k);
			a[1][x1][y1++]=k;
			a[2][x2++][y2]=k;
			a[3][x3][y3--]=k;
			a[4][x4--][y4]=k;
		}
		x1++;y1=1;
		y2--;x2=1;
		x3--;y3=m;
		y4++;x4=m;
	}
	int l,r,mid;
	l=0,r=maxn-minn;
	while(l<r)
	{
		mid=(l+r)>>1;
		if(check(mid))  r=mid;
		else  l=mid+1;
	}
	printf("%d",l);
//	fclose(stdin);
//	fclose(stdout);
	return 0;
}