Luogu-3933 (二分答案)

来源:互联网 发布:托尔金完成的书 知乎 编辑:程序博客网 时间:2024/06/06 09:45

题目

https://www.luogu.org/problemnew/show/P3933#sub
P3933 Chtholly Nota Seniorious
时空限制 1s-3s / 128MB
额,题面有点长,可以去上面链接那里看题吧,下面也会简单说一下题意。

分析

  • 题意就是给一个矩阵,要求把它分成两个区域使得每一个区域中的点都可以两两到达,且最多拐一次弯,求两区域的 极差 中间较大的一个的 最小值 是多少。(一个区域的极差指里面最大最小值得差)
  • 题意中“最多拐一次弯”,可以理解为两部分的分割线要么从上到下递增,要么递减(如图)这里写图片描述
  • 再看到又是这种较大值的最小值,可以考虑二分答案,而且也的确符合二分性质,然后问题就变成怎么判断答案是否可行。
  • 看看数据范围,时间 1~3s ,log还可以乘个 MN,于是可以这样做
    • 设二分的答案为 mid
    • 显然矩阵中最大值最小值是分开在两个区域里的
    • 然后含有最大值Max的那个区域里的所有数都不能小于 Max-mid
    • 同理含有最小值Min的那个区域里的所有数都不能大于Min+mid
    • 可以从上到下扫一遍,记下当前行两区域的区分点最右能到哪,只要这个能保证单调即可。
    • 然后由于分割线有两个方向,Min 和 Max 在两个区间的情况也能有两种,分四类(左上Min,右下Max;左上Max,右下Min;左下Min,右上Max;左下Max,右上Min),四种情况都无解代表答案不可行,否则可行。

程序

#include <cstdio>#include <algorithm>#define For(x,l,r) for(int x=l; x<=r; x++)using namespace std;int n,m,l,r,mid,ans,Max=-1,Min=1000000001,a[2005][2005];bool check(){    int L1=Max-mid,L2=Min+mid,P,k,i,j,F1,F2,F3,F4;    F1=F2=F3=F4=1;    for (i=1,j=1,P=m+1; i<=n && F1; i++,j=1){       //x1>=L1, x2<=L2        while (a[i][j]>=L1 && j<P) j++;        P=j;        for (; j<=m; j++) if (a[i][j]>L2){F1=0; break;};    }    for (i=1,j=1,P=m+1; i<=n &&F2; i++,j=1){        //x1<=L2, x2>=L1        while (a[i][j]<=L2 && j<P) j++;        P=j;        for (; j<=m; j++) if (a[i][j]<L1){F2=0; break;};    }    for (i=n,j=1,P=m+1; i>=1 && F3; i--,j=1){       //x1>=L1, x2<=L2        while (a[i][j]>=L1 && j<P) j++;        P=j;        for (; j<=m; j++) if (a[i][j]>L2){F3=0; break;}    }    for (i=n,j=1,P=m+1; i>=1 && F4; i--,j=1){       //x1<=L2, x2>=L1        while (a[i][j]<=L2 && j<P) j++;        P=j;        for (; j<=m; j++) if (a[i][j]<L1){F4=0; break;}    }    return F1+F2+F3+F4>0;}int main(){    scanf("%d%d",&n,&m);    For(i,1,n) For(j,1,m) scanf("%d",&a[i][j]),Min=min(Min,a[i][j]),Max=max(Max,a[i][j]);    for (l=0,r=1e9,mid=l+r>>1; l<=r; mid=l+r>>1){        if (check()){            r=mid-1; ans=mid; continue;        }        l=mid+1;    }    printf("%d",ans);}
原创粉丝点击