「题解」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;
}

「题解」JOIOI 王国的更多相关文章

  1. 「JOI 2017 Final」JOIOI 王国

    「JOI 2017 Final」JOIOI 王国 题目描述 题目译自 JOI 2017 Final T3「 JOIOI 王国 / The Kingdom of JOIOI」 JOIOI 王国是一个 H ...

  2. 「题解」「美团 CodeM 资格赛」跳格子

    目录 「题解」「美团 CodeM 资格赛」跳格子 题目描述 考场思路 思路分析及正解代码 「题解」「美团 CodeM 资格赛」跳格子 今天真的考自闭了... \(T1\) 花了 \(2h\) 都没有搞 ...

  3. 「题解」「HNOI2013」切糕

    文章目录 「题解」「HNOI2013」切糕 题目描述 思路分析及代码 题目分析 题解及代码 「题解」「HNOI2013」切糕 题目描述 点这里 思路分析及代码 题目分析 这道题的题目可以说得上是史上最 ...

  4. loj#2334 「JOI 2017 Final」JOIOI 王国

    分析 二分答案 判断左上角是否满足 为了覆盖所有范围 我们依次把右下角,左上角,右上角移动到左上角 代码 #include<bits/stdc++.h> using namespace s ...

  5. 「NOIP2018」保卫王国

    「NOIP2018保卫王国」 题目描述 有一棵 \(n\) 个点, 点有点权 \(a_i\),\(m\) 组询问, 每次求钦点两个节点必须选或者必须不选后的树上最小点覆盖. \(1 \leq n, m ...

  6. 「题解」:[loj2763][JOI2013]现代豪宅

    问题 A: 现代豪宅 时间限制: 1 Sec  内存限制: 256 MB 题面 题目描述 (题目译自 $JOI 2013 Final T3$「現代的な屋敷」) 你在某个很大的豪宅里迷路了.这个豪宅由东 ...

  7. 「题解」:[POJ2942]Knights of the Round Table

    问题 E: Knights of the Round Table 时间限制: 1 Sec  内存限制: 256 MB 题面 题目描述 作为一名骑士是一个非常有吸引力的职业:寻找圣杯,拯救遇难的少女,与 ...

  8. 「题解」:$Six$

    问题 A: Six 时间限制: 1 Sec  内存限制: 512 MB 题面 题面谢绝公开. 题解 来写一篇正经的题解. 每一个数对于答案的贡献与数本身无关,只与它包含了哪几个质因数有关. 所以考虑二 ...

  9. 「题解」:$Smooth$

    问题 A: Smooth 时间限制: 1 Sec  内存限制: 512 MB 题面 题面谢绝公开. 题解 维护一个队列,开15个指针,对应前15个素数. 对于每一次添加数字,暴扫15个指针,将指针对应 ...

随机推荐

  1. VS2013 Solution Explorer can not open

    Delete content under the path: C:\Users\username\AppData\Local\Microsoft\VisualStudio\12.0\Component ...

  2. 17,a:img的alt和title有何异同? b:strong与en的异同?

    alt(alt text):为不能显示的图像,窗体或者applets的用户代理,alt属性用来指定替换文字.替换文字的语言用lang属性来指定. eg:下例中将图像作为链接来使用 <a href ...

  3. 用svn客户端checkout时报错RA layer request failed

    用svn客户端checkout时报错: RA layer request failedsvn: Unable to connect to a repository at URL 'https://30 ...

  4. django之反向解析和命名空间

    背景:当我们页面中存放的请求路径与url文件中的url一致时,如果url改了是不是所有的请求路径都要跟着改?显然不现实,这里我们就要用到反向解析. 如下图所示,输入url后会跳转到登录页面,输入用户名 ...

  5. pdf .js和tableexport.js使用时遇到的2问题。

    pdf .js 问题一:报错 network.js:71 The provided value 'moz-chunked-arraybuffer' is not a valid enum value  ...

  6. vue 事件总线(bus)

    1.全局引入bus Vue.prototype.$bus = new.Vue() 2.组件间传值使用(在发送事件时接收组件会实时接收到, 可以用做兄弟组件间相互传值, 但页面跳转组件间有问题 通过$e ...

  7. PTA的Python练习题(十四)

    第4章-10 最大公约数和最小公倍数 m,n=map(int,input().split()) min=0 a=b=0 if m >n: min=m else: min=n for i in r ...

  8. 吴裕雄--天生自然Numpy库学习笔记:NumPy 数据类型

    下表列举了常用 NumPy 基本类型. 名称 描述 bool_ 布尔型数据类型(True 或者 False) int_ 默认的整数类型(类似于 C 语言中的 long,int32 或 int64) i ...

  9. 吴裕雄 Bootstrap 前端框架开发——Bootstrap 网格系统实例:响应式的列重置

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  10. java模板字符串功能的简单实现

    package com.Interface.util; import lombok.extern.slf4j.Slf4j; /** * 测试类 * * @author 华文 * @date 2019年 ...