「题解」JOIOI 王国

题目描述

点这里

考场思考

因为时间不太够了,直接一上来就着手暴力。但是本人太菜,居然暴力爆 000 ,然后当场自闭…

一气之下,发现对 60pts60pts60pts 的数据范围有点思路,然后就开始码。

大概思路是 DPDPDP , 定义状态 dp[i][j]:dp[i][j]:dp[i][j]: 在第 iii 行的划分点是 jjj ,即把第 iii 行分成 [1,j][1,j][1,j] 与 [j+1,M][j+1,M][j+1,M] 。

代码大概长这样:

#include<cstdio>
#include<cstring> #define rep(i,__l,__r) for(int i=__l,i##_end_=__r;i<=i##_end_;++i)
#define fep(i,__l,__r) for(int i=__l,i##_end_=__r;i>=i##_end_;--i)
#define writc(a,b) fwrit(a),putchar(b)
#define mp(a,b) make_pair(a,b)
#define ft first
#define sd second
#define LL long long
#define ull unsigned long long
#define pii pair<int,int>
#define Endl putchar('\n')
// #define FILEOI #ifdef FILEOI
inline char fgetc(){
#define MAXSIZE 500000
static char buf[MAXSIZE+5],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXSIZE,stdin),p1==p2)?EOF:*p1++;
}
#define cg (c=fgetc())
#else
#define cg (c=getchar())
#endif
template<class T>inline void qread(T& x){
char c;bool f=0;
while(cg<'0'||'9'<c)f|=(c=='-');
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
if(f)x=-x;
}
inline int qread(){
int x=0;char c;bool f=0;
while(cg<'0'||'9'<c)f|=(c=='-');
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
return f?-x:x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline int gcd(const int a,const int b){return b?gcd(b,a%b):a;}
inline void getInv(int inv[],const int lim,const int MOD){
inv[0]=inv[1]=1;for(int i=2;i<=lim;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
}
template<class T>void fwrit(const T x){
if(x<0)return (void)(putchar('-'),fwrit(-x));
if(x>9)fwrit(x/10);putchar(x%10^48);
}
inline LL mulMod(const LL a,const LL b,const LL mod){//long long multiplie_mod
return ((a*b-(LL)((long double)a/mod*b+1e-8)*mod)%mod+mod)%mod;
} const int MAXN=2000;
const int MAXM=2000;
const int INF=0x3f3f3f3f; int N,M,a[MAXN+5][MAXM+5],l=INF,r,mid,ans;
int maxl[MAXN+5][MAXM+5],maxr[MAXN+5][MAXM+5];
int minl[MAXN+5][MAXM+5],minr[MAXN+5][MAXM+5];
int dp[MAXN+5][MAXM+5];
int dpmaxl[MAXN+5][MAXM+5],dpminl[MAXN+5][MAXM+5];
int dpmaxr[MAXN+5][MAXM+5],dpminr[MAXN+5][MAXM+5];
//保存极差最大值 int check(){
memset(dp,0x3f,sizeof dp);
rep(j,1,M-1){
dp[1][j]=Max(maxl[1][j]-minl[1][j],maxr[1][j+1]-minl[1][j+1]);
dpmaxl[1][j]=maxl[1][j];
dpminl[1][j]=minl[1][j];
dpmaxr[1][j]=maxr[1][j+1];
dpminr[1][j]=minr[1][j+1];
}
rep(i,2,N)rep(j,0,M)rep(k,j,M){
int pre=Max(Max(dpmaxl[i-1][k],maxl[i][j])-Min(dpminl[i-1][k],minl[i-1][j]),Max(dpmaxr[i-1][k],maxr[i][j+1])-Min(dpminr[i-1][k],minr[i-1][j+1]));
if(pre<dp[i][j]){
dp[i][j]=pre;
dpmaxl[i][j]=Max(dpmaxl[i-1][k],maxl[i][j]);
dpminl[i][j]=Min(dpminl[i-1][k],minl[i-1][j]);
dpmaxr[i][j]=Max(dpmaxr[i-1][k],maxr[i][j+1]);
dpminr[i][j]=Min(dpminr[i-1][k],minr[i-1][j+1]);
}
}
/*
rep(i,1,N){
rep(j,0,M)printf("dp[%d][%d]==%d\n",i,j,dp[i][j]);
Endl;
}
*/
int ret=INF;
rep(j,0,M)ret=Min(ret,dp[N][j]);
return ret;
} inline void init(){
qread(N,M);
rep(i,1,N)rep(j,1,M){
qread(a[i][j]);
l=Min(a[i][j],l);
r=Max(a[i][j],r);
}
rep(i,1,N){
minl[i][0]=INF;
rep(j,1,M){
maxl[i][j]=Max(maxl[i][j-1],a[i][j]);
minl[i][j]=Min(minl[i][j-1],a[i][j]);
}
}
rep(i,1,N){
minr[i][M+1]=INF;
fep(j,M,1){
maxr[i][j]=Max(maxr[i][j+1],a[i][j]);
minr[i][j]=Min(minr[i][j+1],a[i][j]);
}
}
} signed main(){
#ifdef FILEOI
freopen("file.in","r",stdin);
freopen("file.out","w",stdout);
#endif
init();
writc(check(),'\n');
return 0;
}

感觉正确性是可以保证的,但是码出来连样例一都没过,然后就自闭了…

无奈,码出来还剩 2min2min2min ,也没调试的时间了,还不如在座位上自闭一会…

正解

据 JZM\text{JZM}JZM 大佬所说,这道题他打的 二分 + DPDPDP ,这让我感到很迷茫…不过大佬就是大佬…

大概正解思路是这样的:

二分一个最优的答案 midmidmid ,这应该是可以想到的。

但是我们怎么验证这个 midmidmid 的正确性呢?如果用暴力的方法,就是枚举划分区域的样子。

但是这无疑是要超时的 而且还会 T 飞

不知道怎么做?分析一下这道题的特性:

  • 把划分的区域一排一排地看(一列一列地看也可以,看自己喜好喽…),这一定是一个单调不下降或者单调不上升序列
  • 一个区域,一定是其所包含的点越少越好(这一条仔细想想,此处不再赘述)
  • 最优的答案划分中,所有数中的最大值和最小值一定不在同一个区域中

有了这四个特性,对这道题有什么帮助呢?

其中最重要的,是第二条和第三条,那么这两条给我们怎样的启发?

一个区间,包含的东西越少越好,即每增加一个数,最好的情况就是不改变这个区域的极差。

而运气不好的话,会增加这个区域的极差。

那么对于一个区域,它其实并不想要多余的点,但是对于它对面的那个区域,其心中不也是这样想的?

那么就有一个矛盾:两边都不想要点,那怎么分?

其实,如果其中一个区域加上这个点而没有改变它的极差,那么它还是可以要这个点的。

那么,我们可以规定这个区域的极差为 midmidmid ,但是这又有一个问题:

假如说有这样一个区间:最小值为 minnminnminn ,最大值为 maxxmaxxmaxx ,且满足 maxx−minn=midmaxx-minn=midmaxx−minn=mid

那么,如果后来的某个点更改了 minnminnminn ,那么这时的 maxxmaxxmaxx 就不合法了,所以我们还要回头去把那个最大的点去掉。

无疑,这样是很麻烦的。那么我们怎么搞?

千万不要说这样的话,心态要稳住。

这个时候,就该我们的第三点出场了:

看似没用?这里就很好地规避了上面的问题:

一个区域的 最大/最小值 都被规定了,那么只需看其区域中对应的 最小/最大值 是否超过即可。

那么,我们可以规定我们 checkcheckcheck 的区间是包含最大值的,那么只需要将 ≥maxx−mid≥maxx-mid≥maxx−mid 的点尽量包含进这个区间,再看对面的区间是否满足 极差 ≤mid\le mid≤mid 即可。

代码如下:

#include<cstdio>
#include<cstring> #define rep(i,__l,__r) for(int i=__l,i##_end_=__r;i<=i##_end_;++i)
#define fep(i,__l,__r) for(int i=__l,i##_end_=__r;i>=i##_end_;--i)
#define writc(a,b) fwrit(a),putchar(b)
#define mp(a,b) make_pair(a,b)
#define ft first
#define sd second
#define LL long long
#define ull unsigned long long
#define pii pair<int,int>
#define Endl putchar('\n')
// #define FILEOI #ifdef FILEOI
inline char fgetc(){
#define MAXBUFFERSIZE 500000
static char buf[MAXBUFFERSIZE+5],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXBUFFERSIZE,stdin),p1==p2)?EOF:*p1++;
}
#undef MAXBUFFERSIZE
#define cg (c=fgetc())
#else
#define cg (c=getchar())
#endif
template<class T>inline void qread(T& x){
char c;bool f=0;
while(cg<'0'||'9'<c)f|=(c=='-');
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
if(f)x=-x;
}
inline int qread(){
int x=0;char c;bool f=0;
while(cg<'0'||'9'<c)f|=(c=='-');
for(x=(c^48);'0'<=cg&&c<='9';x=(x<<1)+(x<<3)+(c^48));
return f?-x:x;
}
template<class T,class... Args>inline void qread(T& x,Args&... args){qread(x),qread(args...);}
template<class T>inline T Max(const T x,const T y){return x>y?x:y;}
template<class T>inline T Min(const T x,const T y){return x<y?x:y;}
template<class T>inline T fab(const T x){return x>0?x:-x;}
inline int gcd(const int a,const int b){return b?gcd(b,a%b):a;}
inline void getInv(int inv[],const int lim,const int MOD){
inv[0]=inv[1]=1;for(int i=2;i<=lim;++i)inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
}
template<class T>void fwrit(const T x){
if(x<0)return (void)(putchar('-'),fwrit(-x));
if(x>9)fwrit(x/10);putchar(x%10^48);
}
inline LL mulMod(const LL a,const LL b,const LL mod){//long long multiplie_mod
return ((a*b-(LL)((long double)a/mod*b+1e-8)*mod)%mod+mod)%mod;
} const int MAXN=2000;
const int MAXM=2000;
const int INF=0x3f3f3f3f; int N,M,l=INF,r,mid,ans,maxx,minn;
int x[MAXM+5],a[MAXN+5][MAXM+5];
int premax[MAXN+5][MAXM+5],premin[MAXN+5][MAXM+5];
int sufmax[MAXN+5][MAXM+5],sufmin[MAXN+5][MAXM+5]; bool check(const int var){
int down=maxx-var;
// printf("Now down == %d, var == %d\n",down,var); // puts("-----------Case 1:-----------");
x[0]=M;
rep(i,1,N){
x[i]=0;
while(x[i]<x[i-1] && a[i][x[i]+1]>=down)++x[i];
}
// rep(i,1,N)printf("x[%d] == %d\n",i,x[i]);
int tmax=-INF,tmin=INF;
rep(i,1,N){
tmax=Max(tmax,sufmax[i][x[i]+1]);
tmin=Min(tmin,sufmin[i][x[i]+1]);
if(tmax-tmin>var)break;
}
// printf("tmax == %d, tmin == %d\n",tmax,tmin);
if(tmax-tmin<=var)return true; // puts("-----------Case 2:-----------");
x[0]=1;
rep(i,1,N){
x[i]=M+1;
while(x[i]>x[i-1] && a[i][x[i]-1]>=down)--x[i];
}
// rep(i,1,N)printf("x[%d] == %d\n",i,x[i]);
tmax=-INF,tmin=INF;
rep(i,1,N){
tmax=Max(tmax,premax[i][x[i]-1]);
tmin=Min(tmin,premin[i][x[i]-1]);
if(tmax-tmin>var)break;
}
// printf("tmax == %d, tmin == %d\n",tmax,tmin);
if(tmax-tmin<=var)return true; // puts("-----------Case 3:-----------");
x[N+1]=M;
fep(i,N,1){
x[i]=0;
while(x[i]<x[i+1] && a[i][x[i]+1]>=down)++x[i];
}
// rep(i,1,N)printf("x[%d] == %d\n",i,x[i]);
tmax=-INF,tmin=INF;
rep(i,1,N){
tmax=Max(tmax,sufmax[i][x[i]+1]);
tmin=Min(tmin,sufmin[i][x[i]+1]);
if(tmax-tmin>var)break;
}
// printf("tmax == %d, tmin == %d\n",tmax,tmin);
if(tmax-tmin<=var)return true; // puts("-----------Case 4:-----------");
x[N+1]=1;
fep(i,N,1){
x[i]=M+1;
while(x[i]>x[i+1] && a[i][x[i]-1]>=down)--x[i];
}
// rep(i,1,N)printf("x[%d] == %d\n",i,x[i]);
tmax=-INF,tmin=INF;
rep(i,1,N){
tmax=Max(tmax,premax[i][x[i]-1]);
tmin=Min(tmin,premin[i][x[i]-1]);
if(tmax-tmin>var)break;
}
// printf("tmax == %d, tmin == %d\n",tmax,tmin);
if(tmax-tmin<=var)return true; return false;
} inline void init(){
qread(N,M);
rep(i,0,N+1)rep(j,0,M+1)premin[i][j]=sufmin[i][j]=INF,premax[i][j]=sufmax[i][j]=-INF;
rep(i,1,N)rep(j,1,M){
premax[i][j]=premin[i][j]=sufmax[i][j]=sufmin[i][j]=a[i][j]=qread();
l=Min(a[i][j],l);
r=Max(a[i][j],r);
}
rep(i,1,N)rep(j,2,M){
premax[i][j]=Max(premax[i][j],premax[i][j-1]);
premin[i][j]=Min(premin[i][j],premin[i][j-1]);
}
rep(i,1,N)fep(j,M-1,1){
sufmax[i][j]=Max(sufmax[i][j],sufmax[i][j+1]);
sufmin[i][j]=Min(sufmin[i][j],sufmin[i][j+1]);
}
} inline void bisearch(){
maxx=r,minn=l;
while(l<=r){
mid=(l+r)>>1;
// printf("Now l == %d, r == %d, mid == %d, ans == %d\n",l,r,mid,ans);
if(check(mid))ans=mid,r=mid-1;
else l=mid+1;
// puts("____________________________________");
}
} signed main(){
#ifdef FILEOI
freopen("file.in","r",stdin);
freopen("file.out","w",stdout);
#endif
init();
bisearch();
writc(ans,'\n');
return 0;
}
05-11 16:55