[LOJ2334] 「JOI 2017 Final」JOIOI 王國(二分答案)

題意

JOIOI 王國是一個 HHHWWW 列的長方形網格,每個 1×11\times 11×1 的子網格都是一個正方形的小區塊。爲了提高管理效率,我們決定把整個國家劃分成兩個省 JOI 和 IOI 。

我們定義,兩個同省的區塊互相連接,意爲從一個區塊出發,不用穿過任何一個不同省的區塊,就可以移動到另一個區塊。有公共邊的區塊間可以任意移動。
我們不希望劃分得過於複雜,因此劃分方案需滿足以下條件:

  • 區塊不能被分割爲兩半,一半屬 JOI 省,一半屬 IOI 省。
  • 每個省必須包含至少一個區塊,每個區塊也必須屬於且只屬於其中一個省。
  • 同省的任意兩個小區塊互相連接。
  • 對於每一行/列,如果我們將這一行/列單獨取出,這一行/列裏同省的任意兩個區塊互相連接。這一行/列內的所有區塊可以全部屬於一個省。

現給出所有區域的海拔,你需要求出maxmax(JOI省海拔極差,IOI省海拔極差)的最小值。

首先觀察題目,發現合法的劃分一定是階梯型,考慮二分答案,我們發現最大值和最小值不在同一塊。

那麼我們考慮兩種階梯形的情況,每次分別填入最大值和最小值一共四種情況,判定的時候我們可以利用階梯長度遞減的性質,貪心找到一個最優的階梯,每次四種情況一起判定 如果一種可行那麼當前答案就可行。

複雜度O(nmlogL),L=maxminO(nm\log L),L = max - min

#include<bits/stdc++.h>

using namespace std;

int read()
{
	int _ = 0, __ = getchar(), ___ = 1; 
	for(; !isdigit(__); __ = getchar()) if(__ == '-') ___ = -1;
	for(; isdigit(__); __ = getchar()) _ = (_ << 3) + (_ << 1) + (__ ^ 48);
	return _ * ___;
}

const int N = 2000 + 7;

int premax[N][N], sufmax[N][N], premin[N][N], sufmin[N][N];
int tmp[N], n, m, mn = tmp[0] = 2e9, mx = -2e9;

bool check1(int lim)
{
	int mnlim = mn + lim, mxlim = mx - lim, lst = m;
	for(int i = 1; i <= n; ++ i)
	{
		tmp[i] = 0;
		for(int j = 1; j <= m; ++ j)
			if(premax[i][j] <= mnlim) 
				tmp[i] = j; 
	}
	for(int i = n; i >= 1; -- i)
	{
		lst = min(tmp[i], lst);
		if(sufmin[i][lst + 1] < mxlim)
			return false;
	}
	return true;
}

bool check2(int lim)
{
	int mnlim = mn + lim, mxlim = mx - lim, lst = m;
	for(int i = 1; i <= n; ++ i)
	{
		tmp[i] = 0;
		for(int j = 1; j <= m; ++ j)
			if(premin[i][j] >= mxlim) 
				tmp[i] = j; 
	}
	for(int i = n; i >= 1; -- i)
	{
		lst = min(tmp[i], lst);
		if(sufmax[i][lst + 1] > mnlim)
			return false;
	}
	return true;
}

bool check3(int lim)
{
	int mnlim = mn + lim, mxlim = mx - lim, lst = m;
	for(int i = 1; i <= n; ++ i)
	{
		tmp[i] = 0;
		for(int j = 1; j <= m; ++ j)
			if(premax[i][j] <= mnlim) 
				tmp[i] = j; 
	}
	for(int i = 1; i <= n; ++ i)
	{
		lst = min(tmp[i], lst);
		if(sufmin[i][lst + 1] < mxlim)
			return false;
	}
	return true;
}

bool check4(int lim)
{
	int mnlim = mn + lim, mxlim = mx - lim, lst = m;
	for(int i = 1; i <= n; ++ i)
	{
		tmp[i] = 0;
		for(int j = 1; j <= m; ++ j)
			if(premin[i][j] >= mxlim) 
				tmp[i] = j; 
	}
	for(int i = 1; i <= n; ++ i)
	{
		lst = min(tmp[i], lst);
		if(sufmax[i][lst + 1] > mnlim)
			return false;
	}
	return true;
}

bool check(int lim)
{
	if(check1(lim) || check2(lim) || check3(lim) || check4(lim))
		return true;
	return false;
}

int main() 
{
	//freopen("separate.in", "r", stdin);
	//freopen("separate.out", "w", stdout);

	n = read(), m = read();
	for(int i = 1; i <= n; ++ i)
	{
		for(int j = 1; j <= m; ++ j)
		{
			premax[i][j] = premin[i][j] = sufmax[i][j] = sufmin[i][j] = read();
			if(premax[i][j] > mx) 
				mx = premax[i][j];
			if(premin[i][j] < mn)
				mn = premin[i][j];
		}
		sufmin[i][m + 1] = 2e9;
	}
	for(int i = 1; i <= n; ++ i)
	{
		for(int j = 2; j <= m; ++ j)
		{
			premax[i][j] = max(premax[i][j - 1], premax[i][j]);
			premin[i][j] = min(premin[i][j - 1], premin[i][j]);
		}
		for(int j = m; j >= 2; -- j)
		{
			sufmax[i][j - 1] = max(sufmax[i][j], sufmax[i][j - 1]);
			sufmin[i][j - 1] = min(sufmin[i][j], sufmin[i][j - 1]);
		}
	}

	int l = 0, r = mx - mn, ans;
	while(l <= r) 
	{
		int mid = (l + r) >> 1;
		if(check(mid)) 
			ans = mid, r = mid - 1;
		else 
			l = mid + 1;
	}

	printf("%d\n", ans);

	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章