「JOI 2017 Final」JOIOI 王国

「JOI 2017 Final」JOIOI 王国

题目描述

题目译自 JOI 2017 Final T3「 JOIOI 王国 / The Kingdom of JOIOI」

JOIOI 王国是一个 HHH 行 WWW 列的长方形网格,每个 1×11 imes 11×1 的子网格都是一个正方形的小区块。为了提高管理效率,我们决定把整个国家划分成两个省 JOI 和 IOI 。

我们定义,两个同省的区块互相连接,意为从一个区块出发,不用穿过任何一个不同省的区块,就可以移动到另一个区块。有公共边的区块间可以任意移动。

我们不希望划分得过于复杂,因此划分方案需满足以下条件:

区块不能被分割为两半,一半属 JOI 省,一半属 IOI 省。

每个省必须包含至少一个区块,每个区块也必须属于且只属于其中一个省。

同省的任意两个小区块互相连接。

对于每一行/列,如果我们将这一行/列单独取出,这一行/列里同省的任意两个区块互相连接。这一行/列内的所有区块可以全部属于一个省。

现给出所有区块的海拔,第 iii 行第 jjj 列的区块的海拔为 Ai,jA_{i,j}A​i,j​​。设 JOI 省内各区块海拔的极差(最大值减去最小值) 为 RJOIR_{JOI}R​JOI​​,IOI 省内各区块海拔的极差为 RIOIR_{IOI}R​IOI​​。在划分后,省内的交流有望更加活跃。但如果两个区块的海拔差太大,两地间的交通会很不方便。 因此,理想的划分方案是 max(RJOI,RIOI)max(R_{JOI}, R_{IOI})max(R​JOI​​,R​IOI​​) 尽可能小。

你的任务是求出 max(RJOI,RIOI)max(R_{JOI}, R_{IOI})max(R​JOI​​,R​IOI​​) 至少为多大。

输入格式

第一行,两个整数 H,WH,WH,W,用空格分隔。

在接下来的 HHH 行中,第 iii 行有 WWW 个整数 Ai,1,Ai,2,…,Ai,WA_{i,1}, A_{i, 2}, ldots, A_{i, W}A​i,1​​,A​i,2​​,…,A​i,W​​,用空格分隔。

输入的所有数的含义见题目描述。

输出格式

一行,一个整数,表示 max(RJOI,RIOI)max(R_{JOI}, R_{IOI})max(R​JOI​​,R​IOI​​) 可能的最小值。

样例

样例输入 1

4 4

1 12 6 11

11 10 2 14

10 1 9 20

4 17 19 10

样例输出 1

11

样例输入 2

8 6

23 23 10 11 16 21

15 26 19 28 19 20

25 26 28 16 15 11

11 8 19 11 15 24

14 19 15 14 24 11

10 8 11 7 6 14

23 5 19 23 17 17

18 11 21 14 20 16

样例输出 2

18

数据范围与提示

对于 15%15\%15% 的数据,H,W⩽10H, Wleqslant 10H,W⩽10。

对于另外 45%45\%45% 的数据,H,W⩽200H, Wleqslant 200H,W⩽200。

对于所有数据,2⩽H,W⩽2000,Ai,j⩽109(1⩽i⩽H,1⩽j⩽W)2leqslant H, Wleqslant 2000, A_{i,j}leqslant 10^9(1leqslant ileqslant H, 1leqslant jleqslant W)2⩽H,W⩽2000,A​i,j​​⩽10​9​​(1⩽i⩽H,1⩽j⩽W)。

【及其不正经的题解】

不想看的往后翻(我知道你们就只看代码)

咳咳,这道题是是吕神考试的T2。T1是道思维量挺高的贪心,T3是道期望dp,我都懒得做(我绝对不会告诉你是因为我太菜了),所以我开始看T2。一共大概写了3个小时,200行后,我成功A掉了样例,正当我高兴的手舞足蹈,以为要在吕神的考试上切题时,我随手写了个小数据,结果竟然hack掉了我的代码!(我怎么那么欠)我的心态就崩了。怀着忐忑的心情,我把代码交了上去,结果得了6分(旁边低一年级的小学弟交了个错的dfs,拿了个8分)

关键是,我(已删)写的是正解!

我那獐头鼠目百拙千丑鹰头雀脑鹄面鸠形不堪入目鸢肩豺目乌面鹄形的代码就暂且不放在这里了,有想看的可以私聊。下面简单介绍一下我的思路

【题解】

首先,要看懂这题,必须先看懂这句话“对于每一行/列,如果我们将这一行/列单独取出,这一行/列里同省的任意两个区块互相连接。这一行/列内的所有区块可以全部属于一个省”。

这句话是什么意思呢?

我们先来看三个图(A代表joi,B代表ioi)

(1)

AAAAAAAAAB

AAAAAABBBB

AABBBBBBBB

AAAAABBBBB

AAAAAAAABB

(2)

AAAAAAAAAA

AAAAAAAAAA

AAAABAAAAA

AABBBBAAAA

BBBBBBBBAA

(3)

AAAAAAAABB

AAAAAAABBB

AAAAAABBBB

AABBBBBBBB

ABBBBBBBBB

如果你读懂了上面的话,就知道上面三个图中,只有图3是合法的。也就是说,你在切分两个国家时,只能画一条单调的线。

但是一个国家既可以左右延伸,也可以上下延伸。这怎么办呢?

我们可以先写从左开始,向右延伸的方法,然后分别把图旋转90,180,270度,这样四种情况就都齐了。但得到了图该怎样做?

我们注意一下这道题的输出是什么:max(R​JOI​​,R​IOI​​) 尽可能小。最小值最大?没错,就是你二分,出来吧!

我们首先得到最大值与最小值,并且答案肯定小于最大值-最小值。注意(敲黑板),重点来了!

假定最大值在joi,最小值在ioi,我们从左上角逐行扫荡,每行的joi王国方格数不超过上一行的(不考虑每行方格数不少于上一行的这种情况)

二分一个答案,只要当前二分中点mid大于等于这个方格与最大值就选它去构造ioi王国,然后再去判断ioi王国中的元素是不是都满足条件

只要不符合就跳转到下一行。

想到了这里,这题就基本结束了。是不是很神奇呢?

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue> 
using namespace std;
#define INF 0x3f3f3f3f
int n,m,a[2010][2010],ans=INF,maxn=-INF,minn=INF;
inline int read()//小小的读入优化,可以不写 
{
    int x=0,f=1; char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=ch-'0'+(x<<3)+(x<<1);
    return x*f;
}

int MAX(int a,int b)
{
    if(a>b) return a;
    if(a<=b) return b;
}

int MIN(int a,int b)//手打好像快一点 
{
    if(a<b) return a;
    if(a>=b) return b;
}

void turn()//旋转90度 
{
    for(int i=1;i<=n;i++)
     for(int j=1;j<=m/2;j++)
      swap(a[i][j],a[i][m-j+1]);
}

void choose()//选择
{
    for(int i=1;i<=n/2;i++)
      for(int j=1;j<=m;j++)
        swap(a[i][j],a[n-i+1][j]);
}

bool check(int mid) 
{
    int f=m+1;
    for(int i=1;i<=n;i++)
    {
        int t=0;
        for(int j=1;j<=min(f,m);j++)
          if(maxn-a[i][j]<=mid) t=max(t,j);//找i行的最大延伸
            else break;
        f=t;
        for(int j=t+1;j<=m;j++)
          if(a[i][j]-minn>mid) return 0;//判断满足条件
    }
    return 1;
}

int find_ans()//二分 
{
    int l=0,r=maxn-minn+1;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(check(mid)) r=mid-1;
        else l=mid+1;
    }
    return l;
}

void find_ans2()//枚举4种情况
{
    ans=min(ans,find_ans());
    turn();
    ans=min(ans,find_ans());
    choose();
    ans=min(ans,find_ans());
    turn();
    ans=min(ans,find_ans());
}

int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++)
      for(int j=1;j<=m;j++)
      {
            a[i][j]=read();
          maxn=max(a[i][j],maxn);
          minn=min(a[i][j],minn);
      }   
    find_ans2(); 
    printf("%d
",ans);
    return 0;
}

本蒟蒻第一篇题解十分不易,希望能帮到大家,谢谢

原文地址:https://www.cnblogs.com/mxrmxr/p/9686500.html