「题解」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. plupload上传视频插件jQuery+php

    我在网上找到一个很好的视频上传插件,经过我的一些整理.补充,在这里分享给大家. 这个视频插件是新浪微博plupload上传视频插件,支持格式有mpg,m4v,mp4,flv,3gp,mov,avi,r ...

  2. IIS反向代理配置教程(最终完整版本)

    IIS代理配置教程 插件下载:https://download.csdn.net/download/song_yan_/11996489 一.安装反向代理插件 1.rewrite插件安装 (1) 双击 ...

  3. 数据库中的null,踩坑笔记

    问题来源:查询表中,complete_type 不为2的数据. 我的想法:select * from 表名 where complete_type != '2'  (除了第2条,全部显示,没问题呀) ...

  4. codeforces-Three Friends

      Three Friends Three friends are going to meet each other. Initially, the first friend stays at the ...

  5. JZOJ5915 [2018NOIP模拟] 明日之星(广义后缀自动机,线段树)

    题目描述 给定一棵树,每个节点有一个权值 \(a_i\) 和一个字符串 \(s_i\). q组询问,每次询问一个字符串 S 和两个节点x,y: 求x到y路径上每个节点的字符串在 S 中出现的次数乘上各 ...

  6. jquery-1.10.2_d88366fd.js和jquery-3.1.0.min.js 在用touch事件时候, event.changedTouches[0]报错的问题。

    1.animation动画:(注意如果这个动画是一开始就执行的,在pc端就要用px,在手机端用rem,如果在pc端展示页面,但用的是rem为单位,这时候动画一开始就执行,因为根字体大小还没准备好,动画 ...

  7. 什么是Maven? 使用Apache Maven构建和依赖项管理

    通过优锐课java架构学习中,学到了不少干货,整理分享给大家学习. 开始使用最流行的Java构建和依赖管理工具Maven Apache Maven是Java开发的基石,也是Java使用最广泛的构建管理 ...

  8. 【Go语言系列】第三方框架和库——GIN:快速入门

    要求要安装Gin软件包,需要:1.安装Go(需要1.11+版本)2.设置Go工作区 安装1.下载并安装 gin: $ go get -u github.com/gin-gonic/gin 2.将 gi ...

  9. 鸡汤 - Choice is yours

    传送门 https://kamranahmed.info/blog/2018/03/24/choice-is-yours/ Our whole lives are driven by the choi ...

  10. redis基本操作,基于StringRedisTemplate,存储,取值,设置超时时间,获取超时时间,插入list操作

    @Autowired private StringRedisTemplate stringRedisTemplate; @GetMapping("/test") void test ...