二分check的妙用

一般对于不容易正向求解的题,我们往往可以枚举答案并进行check,而对于具有单调性的答案或其映射来说,我们可以二分答案并进行check。

例题1:

codeforces 1288 D - Minimax Problem  (传送门)

题意:

给定n行 长为m的序列;

找出任意两行序列,使得 两行序列 所有 两个相同索引的最大值 的最小值最大;

分析:

这道题朴素做法是O(n^2) ,但想在规定时间复杂度正向推出此题答案是非常不容易的;

我们可以考虑枚举 最大的最小值,再用check函数加以检验,

设当前枚举的答案为x,

对于比x小的数,令其为0,否则令其为1,最后判断两个序列对应的01串的相或的结果是否为 全1,就能判断这个x是否可能成立(本来 答案是5 ,而x是3,但是相或也能得到 全1,虽然x可能不是题目要求的bi,但是由于要求解最值,所以check成功的最大的x一定是bi),而我们则是要找到这个最大的可能;

显然,x是否可能成立是具有单调性的,所以可以用二分答案;

注意,题目的m最大为8,而01排列最多就只有 2^8种,我们可以枚举01排列 进行check,而不是O(n^2)枚举所有序列。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
const int N=1e6+5;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const double eps=1e-6;
const long double pi=acos(-1.0L);
#define ls (i<<1)
#define rs (i<<1|1)
#define fi first
#define se second
#define pb push_back
#define mk make_pair
#define mem(a,b) memset(a,b,sizeof(a))
LL read()
{
    LL x=0,t=1;
    char ch;
    while(!isdigit(ch=getchar())) if(ch=='-') t=-1;
    while(isdigit(ch)){ x=10*x+ch-'0'; ch=getchar(); }
    return x*t;
}
int lim,n,m,ansl,ansr;//lim是全1
int a[N][10],vis[305];
int judge(int x)
{
    mem(vis,0);
    for(int i=1;i<=n;i++)
    {
        int t=0;
        for(int j=1;j<=m;j++)
            if(a[i][j]>=x) t|=1<<(j-1);//标记所有01排列
        vis[t]=i;
    }
    for(int i=0;i<=lim;i++)
        for(int j=i;j<=lim;j++)
            if((i|j)==lim&&vis[i]&&vis[j])
            {
                ansl=vis[i]; ansr=vis[j];
                return 1;
            }
    return 0;
}
int main()
{
    n=read(),m=read();
    lim=(1<<m)-1;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            a[i][j]=read();
    int l=0,r=1e9;
    ansl=1,ansr=1;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(judge(mid)) l=mid+1;
        else r=mid-1;
    }
    printf("%d %d
",ansl,ansr);
    return 0;
}
原文地址:https://www.cnblogs.com/DeepJay/p/12225769.html