「题解」JOIOI 王国

「题解」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=midmaxxminn=mid

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

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

这样搞不行,那样搞也不行,我 ** 还不如不搞了…

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

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

  • 最优的答案划分中,所有数中的最大值和最小值一定不在同一个区域中

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

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

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

代码如下:

#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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值