[AT2444]JOIOI王国

题目

传送门 to luogu

题目描述
J O I O I JOIOI JOIOI 王国是一个 h h h w w w 列的长方形网格,每个 的子网格都是一个正方形的小区块。为了提高管理效率,我们决定把整个国家划分成两个省 J O I JOI JOI I O I IOI IOI这个名字蠢透了。

我们定义,两个同省的区块互相连接,意为从一个区块出发,不用穿过任何一个不同省的区块,就可以移动到另一个区块。有公共边的区块间可以任意移动。

我们不希望划分得过于复杂,因此划分方案需满足以下条件:

  • 能否将一个区块被分割为两半,一半属 J O I JOI JOI 省,一半属 I O I IOI IOI 省?不能。
  • 每个省必须包含至少一个区块,每个区块也必须属于且只属于其中一个省。
  • 同省的任意两个小区块互相连接。
  • 对于每一行(列),如果我们将这一行(列)单独取出,这一行(列)里同省的任意两个区块互相连接。这一行(列)内的所有区块可以全部属于一个省。

现给出所有区块的海拔,第 i i i 行第 j j j 列的区块的海拔为 a i j a_{ij} aij 。设 J O I JOI JOI 省内各区块海拔的极差(最大值减去最小值)为 R J O I R_{_{JOI}} RJOI ,而 I O I IOI IOI 省内各区块海拔的极差为 R I O I R_{_{IOI}} RIOI 。在划分后,省内的交流有望更加活跃。但如果两个区块的海拔差太大,两地间的交通会很不方便。 因此,理想的划分方案是 D = max ⁡ ( R J O I , R I O I ) D=\max(R_{_{JOI}},R_{_{IOI}}) D=max(RJOI,RIOI) 尽可能小。

你的任务是求出 D D D 至少为多大。

输入格式
第一行,两个整数 h , w h,w h,w ,用空格分隔。

在接下来的 h h h 行中,第 i i i 行有 w w w 个整数 a i 1 , a i 2 , a i 3 , … , a i w a_{i1},a_{i2},a_{i3},\dots,a_{iw} ai1,ai2,ai3,,aiw ,用空格分隔。

输入的所有数的含义见题目描述。

输出格式
一行,一个整数,表示 D D D 可能的最小值。

思路

直接将某个角落作为一个省,至少有 max ⁡ a − min ⁡ a \max_a-\min_a maxamina 的答案。

不妨设 max ⁡ a ∈ J O I , min ⁡ a ∈ I O I \max_a\in JOI,\min_a\in IOI maxaJOI,minaIOI (否则一定不优)。

此时我们就要 min ⁡ a ∈ J O I \min_a\in JOI minaJOI 最大。最小值最大?二分答案 min ⁡ J O I \min JOI minJOI

写成奇怪格式:(就是值域啦)

J O I : [ x , max ⁡ a ] I O I : [ min ⁡ a , min ⁡ a + ( max ⁡ a − x ) ] JOI:[x,\max_a]\\ IOI:[\min_a,\min_a+(\max_a-x)] JOI:[x,amax]IOI:[amin,amin+(amaxx)]

I O I IOI IOI 的最大值之所以是那个玩意儿,是因为这样的取值不会影响答案 D = max ⁡ a − x D=\max_a-x D=maxax

如何判断是否可行? d p \tt{dp} dp 扫描一遍就是了!

根据题意,发现可能的方案其实很少,应该是如图所示的样子——

在这里插入图片描述

没错,每一行都有清晰的分界线,并且分界线一定是不断右移(或左移)的。每一行的分界线左边隶属于同一个州。读者自证不难。

所以用 f ( x , y , 0 / 1 / 2 ) f(x,y,0/1/2) f(x,y,0/1/2) 表示前 x x x 行,第 x x x 行的分界线是 y y y (即,左边的 y y y 个属于同一个州),分界线曾经有这样的案底: 1 1 1 左移、 2 2 2 右移、 0 0 0 没移。

转移也很简单。记得用上二分的值的判断!

代码

代码实现有个技巧——将列翻转,再跑一遍 d p \tt{dp} dp

这样就枚举了分界线左边是 J O I JOI JOI 和右边是 J O I JOI JOI 的情况。

一个州是否有区块不需要判断。有总比没有好。

#include <cstdio>
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
inline int readint(){
	int a = 0, f = 1; char c = getchar();
	for(; c<'0' or c>'9'; c=getchar())
		if(c == '-') f = -1;
	for(; '0'<=c and c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(long long x){
	if(x < 0) putchar('-'), x = -x;
	if(x > 9) writeint(x/10);
	putchar(x%10+'0');
}

const int MaxN = 2005;
int a[MaxN][MaxN], h, w, max_a, min_a = (1<<30)-1;

void input(){
	h = readint(), w = readint();
	for(int i=1; i<=h; ++i)
		for(int j=1; j<=w; ++j){
			a[i][j] = readint();
			if(max_a < a[i][j])
				max_a = a[i][j];
			if(a[i][j] < min_a)
				min_a = a[i][j];
		}
}

bool dp[MaxN][MaxN][3];
// joi的最右边一个区块在哪里 
// 0:没推 1:左推 2:右推 
bool check(int x,int y){
	for(int i=1; i<=h; ++i){
		for(int j=0; j<=w; ++j)
			dp[i][j][0] = dp[i-1][j][0];
		for(int j=0; j<=w; ++j){
			dp[i][j][2] = dp[i-1][j][2] or dp[i-1][j][0];
			if(j) dp[i][j][2] = dp[i][j][2] or dp[i][j-1][2];
		}
		for(int j=w; ~j; --j){
			dp[i][j][1] = dp[i-1][j][1] or dp[i-1][j][0];
			if(j != w) dp[i][j][1] = dp[i][j][1] or dp[i][j+1][1];
		}
		bool f = true; // 检查[x,max_a] 
		for(int j=1; j<=w; ++j){
			f = f and a[i][j] >= x;
			dp[i][j][0] = f and dp[i][j][0];
			dp[i][j][1] = f and dp[i][j][1];
			dp[i][j][2] = f and dp[i][j][2];
		}
		f = true; // 检查[min_a,y] 
		for(int j=w-1; ~j; --j){
			f = f and a[i][j+1] <= y;
			dp[i][j][0] = f and dp[i][j][0];
			dp[i][j][1] = f and dp[i][j][1];
			dp[i][j][2] = f and dp[i][j][2];
		}
// 这里本来可以剪枝,但是优化效果可能不大,就没打 
// 如果本行dp数组已经全部为false,就return false 
	}
	for(int j=0; j<=w; ++j)
		if(dp[h][j][0] or dp[h][j][1] or dp[h][j][2])
			return true;
	return false;
}
void solve(){
	for(int j=0; j<=w; ++j)
		dp[0][j][0] = true; // 初始化 
	int ans = max_a-min_a; // 至多是这么大 
	int L = min_a, R = max_a;
	while(L != R){
		int mid = (L+R+1)>>1;
		if(check(mid,min_a+max_a-mid)) L = mid;
		else R = mid-1;
	}
	ans = min(ans,max_a-L);
	for(int i=1; i<=h; ++i) // 列翻转! 
		for(int j=1; j<w+1-j; ++j)
			swap(a[i][j],a[i][w+1-j]);
	L = min_a, R = max_a; // do it again ~ 
	while(L != R){
		int mid = (L+R+1)>>1;
		if(check(mid,min_a+max_a-mid)) L = mid;
		else R = mid-1;
	}
	ans = min(ans,max_a-L);
	writeint(ans), putchar('\n');
}

int main(){
	input(), solve();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值