分离计划

分离计划

考察点:二分答案

先说一句话:最近老是做着二分答案的题然后一点思路都没有连二分都看不出来。。。这方面的洞察力要锻炼啊!

题面

众所周知,小Z拥有者足以毁灭世界的力量,可惜他不能控制这份力量,小J和小Z的关系十分亲密,一天小J预感到了小Z体内的力量将要爆发。

这次爆发的力量比以往都要强大,以至于将小Z分为了两个整体,彼此之间靠着万有引力互相靠近,一旦融合,世界将不复存在。

为了拯救世界,小J决定打造一个容器G,将小Z的两个部分分别装在容器G的一个部分,用以控制小Z。

容器由个魔法水晶组成,他们组成了一个行列的矩阵,每个魔法水晶都有自己的能量值,容器需要被分为两个部分,使得每个魔法水晶都属于且仅属于一个部分,并且任何一个魔法水晶都可以在矩阵中只经过和自己属于同一部分的魔法水晶由一条最多改变一次方向的路径抵达另一个和他处于同一部分的魔法水晶。

例如下面的1,2是不合法的容器,3是合法的容器。

265431584.JPG

对于每一个部分,他的不稳定性是属于这个部分的所有魔法水晶能量值的极差(最大-最小)。对于整个容器,不稳定性是两部分不稳定性中的最大值。

为了知道自己能不能拯救世界,不白白浪费时间,小J想知道整个容器的最小的不稳定值。

输入格式

第一行两个数代表魔法水晶组成的矩阵大小。

随后行,每行个整数表示魔法水晶的能量值。

输出格式

一行一个整数,表示最小的不稳定值。


因为题目说“两块极差最大值的最小值”,所以明显有着二分答案的性质。很容易想到,我们要求的极差的极值就是整个矩阵中最小的减去最大的,而极差最小就是0,所以可以对(0,Max_all-Min_all)区间进行二分,大概要进行30次。

由于上面常数log带来的30的常数,我们需要在O(n * m)的时间复杂度内检查二分的mid值是否合法。

怎么办呢

首先,如果图形是合法的,就一定不会出现凹型或凸型。对于一个部分内的水晶,每一排一定是从相同的一边的起始位置开始出现,并且每排的水晶块数都统一大于或小于上一层块数(每排数量单增或单减)。

接下来,知道了图形结构以后,开始检查mid值是否合法。每次算一个不确定的区域内的极差很慢,但如果一个区域内包含了整个矩形的最大值,那么计算极差时只用看最小的那一个数就行了。同理,整个矩形的最小值也是这样。

所以,我们拿到一个mid值,先假设我们将要找到符合条件的两块,且左边一块每行相对于上一行块数单调递减的情况,而且全局最大值在左块,全局最小值在右块。设当前讨论到第i行第j个数,且这个数属于左块,记录一个Last,表示上一行最远可以到达第Last个数时都是满足要求的。显然,j<=Last。
既然我们要求答案要是mid,那么左块的最大值(也就是全局最大值)减去任意一个a[i][j]需要<=mid,这样枚举每一行就可以找到左块每行的边界。

对于右块,由于我们已经处理了左块的边界,那么剩下的不就是右块的了么?那么对于右块的数,采用与处理左块相似的方法。既然我们要求答案要是mid,那么右块的任意一个a[i][j]减去右块最小值(也就是全局最小值)需要<=mid。如果右块有任意一个方块不满足这个条件,那么就应把mid调大。最后如果整个矩阵都满足条件,那么就考虑把mid值调小。(二分答案)

这样,就可以在一个不严格的O(n^2)的时间复杂度内检查答案。

但是我们只考虑了左右两块,左边一块每行相对于上一行块数单调递减,且全局最大值在左块,全局最小值在右块的情况。漏了其他情况。其实也不难,就把整个矩阵镜像一下,跑四次二分答案,就可以求出所有情况了。

代码:

#include<stdio.h>
#include<bits/stdc++.h>
#define ll long long
#define reint register int
#define relong register long long
#define intinf 1e9
#define llinf 1e17
using namespace std;

char buf[1<<20],*p1,*p2;
#define GC (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?0:*p1++)
//#define GC getchar()
inline ll read_64(){ll X=0,w=0;char ch=0; while(!isdigit(ch)){w|=ch=='-';ch=GC;} while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=GC;return w?-X:X;}
inline int read_32(){int X=0,w=0;char ch=0; while(!isdigit(ch)){w|=ch=='-';ch=GC;} while(isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=GC;return w?-X:X;}

const int maxn=2010;
int a[maxn][maxn],ma=-intinf,mi=intinf,ans=intinf,n,m;

void spin1()//左右镜像 
{
    for(reint i=1;i<=n/2;i++)
      for(reint j=1;j<=m;j++)
      {
        swap(a[i][j],a[n-i+1][j]);
          }
}

void spin2()//上下镜像 
{
    for(reint i=1;i<=n;i++)
      for(reint j=1;j<=m/2;j++)
      {
        swap(a[i][j],a[i][m-j+1]);
      }
}

bool check(int x)
{
    int Last=m;
    for(reint i=1;i<=n;i++)
    {
        for(reint j=1;j<=Last;j++)
          if(ma-a[i][j]>x)
          {
            Last=j-1;break;
          }
        for(reint j=Last+1;j<=m;j++)
          if(a[i][j]-mi>x)
            return false;
    }
    return true;
}

void binary_search(int l,int r)
{
    int mid;
    while(l<=r)
    {
        mid=((l+r)>>1);
        if(check(mid)) r=mid-1;
        else l=mid+1;
    }
    while(check(l)) l--;
    l++; 
    ans=min(ans,l);
}

int main()
{
    n=read_32();m=read_32();
    for(reint i=1;i<=n;i++)
    {
        for(reint j=1;j<=m;j++)
        {
            a[i][j]=read_32();
            mi=min(mi,a[i][j]);
            ma=max(ma,a[i][j]);
                }
    }
    binary_search(0,ma-mi);
    spin1();
    binary_search(0,ma-mi);
    spin2();
    binary_search(0,ma-mi);
    spin1();
    binary_search(0,ma-mi);
    printf("%d",ans);
    return 0;
}
/*
4 4
1 12 6 11
11 4 2 14
10 1 9 20
4 17 13 10
*/

其实对于每次检查答案,我们可以先O(n^2)预处理出每一行的前缀最小值,后最最小值数组,每次在里面二分查找判断mid是否合法,可以把O(n^2)的检查降到O(nlogn)。可是我太弱了打不出来

猜你喜欢

转载自www.cnblogs.com/playerzmr/p/11802755.html