[loj2334][JOI 2017 Final]JOIOI 王国——二分答案+贪心
题目大意:
JOIOI 王国は H 行 W 列のマスに区切られた長方形の形をしている.JOIOI 王国では,行政の効率化のた
め,国全体を 2 つの地域 JOI と IOI に分けることにした.
地域の分け方が複雑になりすぎるのを防ぐため,以下の条件を満たすように分割を行うことにした:
• 各地域は,1 つ以上のマスを含む.
• それぞれのマスは,2 つの地域のうちのちょうど 1 つに属する.
• 地域 JOI に属するどの 2 つのマスの間も,その地域に属さないマスに入らずに辺で接しているマス
への移動を繰り返すことにより移動できる.地域 IOI についても同様である.
• 各行,各列について,その行または列に属するマス全体を取り出したとき,それぞれの地域のマスは
ひとつながりになっている.ただし,その行あるいは列のすべてのマスが同じ地域に属していても
よい.
各マスには標高と呼ばれる整数が定まっている.地域への分割を行った後は,各地域内での移動が活発
になると想定される.地域内での標高の差があまりに大きいと移動が大変であるため,同じ地域内での標
高差ができるだけ小さくなるように分割を行いたい.すなわち,地域 JOI の中での標高の最大値と最小値
の差と,地域 IOI の中での標高の最大値と最小値の差のうち,大きい方の値を最小化したい.
思路:
显然最后的图形是一个阶梯形。
好像直接做不是特别好做,考虑一下题目有哪些性质。
不难发现最后的方案中,整个矩阵的最大值和整个矩阵的最小值一定是分别放在两个不同的区域当中。
于是我们可以考虑去枚举拥有最小值的那个区域的最大值,然后贪心地选尽量多的点,这样就可以得到另外一个区域的极差。
不难发现这个函数是凸的,好像可以三分,但是有一点小问题。
于是考虑二分答案,设最优的答案为x,依旧去贪心地选择最小值所在的那个区域,不难发现这样有4种情况,于是我们将最开始的矩形变换三次,贪心之后可得另外一半的极差,检查两部分极差的最大值是否小于x即可。
不难证明答案具有单调性。
#include<bits/stdc++.h>
#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
#define debug(x) cout<<#x<<"="<<x<<endl
typedef long long ll;
using namespace std;
void File(){
freopen("loj2334.in","r",stdin);
freopen("loj2334.out","w",stdout);
}
template<typename T>void read(T &_){
T __=0,mul=1; char ch=getchar();
while(!isdigit(ch)){
if(ch=='-')mul=-1;
ch=getchar();
}
while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
_=__*mul;
}
const int maxn=2000+10;
const int inf=0x3f3f3f3f;
int n,m,a[maxn][maxn],b[maxn][maxn],c[maxn][maxn],d[maxn][maxn],Min=inf,Max=0;
int h[maxn];
void init(){
read(n); read(m);
REP(i,1,n)REP(j,1,m){
read(a[i][j]);
b[i][j]=c[i][j]=d[i][j]=a[i][j];
Min=min(Min,a[i][j]);
Max=max(Max,a[i][j]);
}
REP(i,1,n/2)swap(b[i],b[n-i+1]),swap(c[i],c[n-i+1]);
REP(i,1,n)REP(j,1,m/2)swap(c[i][j],c[i][m-j+1]),swap(d[i][j],d[i][m-j+1]);
}
bool check(int x,int A[maxn][maxn]){
memset(h,0,sizeof(h));
h[n+1]=m;
DREP(i,n,1)while(h[i]<h[i+1] && A[i][h[i]+1]-Min<=x)++h[i];
int mn=inf;
REP(i,1,n)REP(j,h[i]+1,m)mn=min(mn,A[i][j]);
return (Max-mn<=x);
}
bool judge(int x){
return check(x,a) || check(x,b) || check(x,c) || check(x,d);
}
void work(){
int l=0,r=Max-Min;
while(l<r){
int mid=(l+r)>>1;
if(judge(mid))r=mid;
else l=mid+1;
}
printf("%d\n",l);
}
int main(){
//File();
init();
work();
return 0;
}