二分—单调性优化

题面传送门
算法简介:二分查找(答案),时间复杂度 O ( l o g 2 n ) O(log^2n) O(log2n).可以在一个有序表中快速查找某个数。可以利用这个性质来二分答案来做到快速有效枚举。
算法实现:对于一个有序表,二分查找一个数是否存在。
首先定义边界值: l = 0 l=0 l=0, r = m a x ( a i ) + 1 r=max(a_{i})+1 r=max(ai)+1;然后取中值 m i d = ( l + r ) 2 mid=\frac{(l+r)}{2} mid=2(l+r)
这是一个有序表,满足 a i − 1 ≤ a i ≤ a i + 1 a_{i-1}\leq a_i\leq a_{i+1} ai1aiai+1 a i − 1 ≥ a i ≥ a i + 1 a_{i-1}\geq a_i\geq a_{i+1} ai1aiai+1
判断中点是否大于待查找数。若大于,则收拢左边界。反之则收拢右边界。则一定能找到待查找值是否存在。
复杂度: l o g 2 ( m a x ( a i ) + 1 ) log^2(max(a_i)+1) log2(max(ai)+1)
个人理解:挺有用的,但有局限性,必须在有序表中查询。
代码实现:

#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,a[100039],x,y,flag,l,r,mid;
inline void read(int &x){
    char s=getchar(); int f=1;x=0;
    while(s<'0'||s>'9'){if(s=='-') f=-1;s=getchar();}
    while(s>='0'&&s<='9') x=(x<<3)+(x<<1)+(s^48),s=getchar();
    x*=f;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) read(a[i]);
    sort(a+1,a+n+1);
    while(m--){
        flag=0; 
        read(x);
        l=1;r=n;
        while(l<r){
            mid=(l+r)>>1;
            if(a[mid]==x||a[l]==x||a[r]==x){printf("YES\n");flag=1;break;}
            if(a[mid]>x) r=mid-1;
            else if(a[mid]<x) l=mid+1;
        }
        if(!flag) printf("NO\n");
    }
    return 0;
}

二分应用:
1 1 1:二分答案基础题(好吧第一次打这道题用了一个小时):
对于这道题,我们用 d a t a i data_i datai表示每小段长度为 i i i时能切割出的小段数。
d a t a i data_i datai可以表示为 ∑ k = 1 n ⌊ a k i ⌋ \sum\limits_{k=1}^{n}{\left\lfloor\dfrac{a_k}{i}\right\rfloor } k=1niak 则当 i < j < k i<j<k i<j<k时, d a t a i ≥ d a t a j ≥ d a t a k data_i\geq data_j\geq data_k dataidatajdatak。满足二分答案的性质。
所以可以直接二分然后验证。对于答案小于 m m m,收拢右边界,反之收拢左边界。
代码实现:

#include<cstdio>
using namespace std;
int n,m,a[100039],l,r,mid,ans,flag;
int main(){
    register int i;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++) scanf("%d",&a[i]);
    l=0;r=100000001;
    while(l+1<r){
        mid=(l+r)>>1;
        ans=0;
        for(i=1;i<=n;i++) ans+=a[i]/mid;
        if(ans<m) r=mid;
        else l=mid; 
    }
    printf("%d",l);
}

2 2 2:二分答案普及题(好吧第一次打这道题用了 n n n个小时, n > 2 n>2 n>2):
我们发现,当 i < j i<j i<j时,当i订单不满足要求时,j订单一定不满足要求。所以这是一个二分找 01 01 01交接点的问题。
当我们二分答案时可以借助差分数组验证。
代码实现:

#include<bits/stdc++.h>
using namespace  std;
int n,m,s[1000001],d[1000001],q[1000001],a[1000001],b[1000001],c[1000001],l,r,mid,flag;
int main() {
    //freopen("1.in","r",stdin);
    scanf("%d%d",&n,&m);
    for(register int i=1;i<=n;i++) scanf("%d",&s[i]);
    for(register int i=1;i<=m;i++)scanf("%d%d%d",&a[i],&b[i],&c[i]);
    l=0,r=m+1;
    while(l+1<r){
        flag=0;
        memset(d,0,sizeof(d));
        mid=(l+r)>>1;
        for(register int i=1;i<=mid;i++) d[b[i]]+=a[i],d[c[i]+1]-=a[i];
        for(register int i=1;i<=n;i++){
            d[i]+=d[i-1];
            if(d[i]>s[i]){flag=1;break;}
        }
        if(flag) r=mid;
        else l=mid;
    }
    if(l==m) printf("0");
    else printf("-1\n%d",r);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值