二分答案 / 整体二分学习笔记

本讲内容分为两个部分;

第1部分:二分答案介绍(淦,不是讲PJ算法)

第2部分:整体二分介绍(我们是讲整体二分)

Part 1 二分答案介绍

相信大家都对二分答案比较熟悉。

至少...需要会二分查找...

我们考虑对于决策单调的问题:

给出一个长度为n的递增数列,现在有m个询问,每个询问有一个参数$k_i$

对于每一个询问输出这个递增序列中的大于等于$k_i$的位置最靠前的项的位置。

如果没有输出$-1$

对于100%的数据$ n,m \leq 10^6$

这个就大概是二分查找的模板题了,我们当考虑的是当前搜区间是$[L,R]$

其中间项$M=\left \lfloor \frac{L+R}{2} \right \rfloor$大于还是小于$k_i$

如果严格$a_M \geq k_i$,那么搜$[L,M]$否则搜$[M+1,R]$

显然这段代码就可以干这件事情

# include <bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int a[N],n,m;
int work(int k)
{
    int l=1,r=n,ans=-1;
    while (l<=r) {
        int m=(l+r)/2;
        if (a[m]>=k) r=m-1,ans=m;
        else l=m+1;
    }
    return ans;
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    while (m--) {
        int k; scanf("%d",&k);
        printf("%d\n",work(k));
    }
    return 0;
}
二分查找lower_bound()实现

不作赘述。

这里写图片描述

满足决策单调性的事情可以用二分答案转化为判定类问题。

给出二分答案模板:

int l=-inf,r=inf,ans;
while (l<=r) {
    int mid=(l+r)>>1;
    if (check(mid)) ans=mid,l=mid+1; //向更优解尝试
    else r=mid-1; //向更劣解尝试
}
return ans;

然而今天的重点不是这个,这个有点low

Part 2 整体二分介绍

看完上面二分答案是不是感觉这篇blog在水?

其实没有,整体二分是二分答案的加强版更加优美、便捷、不易打挂。

其实本质就是对于操作区间$[ql,qr]$,保证其答案在$[L,R]$中,然后通过$[L,R]$的二分然后逐步缩小,直到L=R,求出$[ql,qr]$的答案是L

上面讨论了一个最显然的问题,考虑如果在上述定义下$[ql,qr]$,保证其答案在$[L,R]$中,当$L=R$时有,$[ql,qr]$答案都为L

对于每一个属于[ql,qr]的询问,考虑当前答案$M=\left \lfloor \frac{L+R}{2} \right \rfloor$是否对其有贡献,如果有累加贡献。

然后对于每一个属于[ql,qr]的询问,考虑之前累加贡献+新增贡献期望贡献值比较,

如果期望贡献值 >= 累加贡献+新增贡献 那么答案就需要比M要大or等(这里按照正相关)

如果期望贡献值 <= 累加贡献+新增贡献 那么答案就需要比M要小or等(这里按照正相关)

然后回退此次操作的更新数据结构的值,保证复杂度只和qr-ql+1有关

最后期望答案小于等于M的和期望答案大于M的分别分治solve即可。

还记得上次[可持久化数据结构学习笔记]中学习的主席树吗?

题目1 : 给出模板题目: P3834 【模板】可持久化线段树 1(主席树)

我们使用树状数组维护当前的贡献[本质是维护位置,值才是贡献],这里的累加贡献和新增贡献就是[x,y]树状数组维护的区间和

这里的期望贡献值就是K。

主要是循环里面讨论的问题:

1.是更改操作[pos,x]

    如果 更改位置上的值x > M 那么对贡献无影响,放到q2里面 , 更大的才会用到他

  如果 更改位置上的值x <=M 那么对答案有影响,放到q1里面,已经加入更大的已经加过了[表现为减去...],在树状数组维护的相应位置上pos + 1

2.是查询操作[l,r,k]

  设w为 查询 [l,r]树状数组维护有w个答案在此下标范围内。

  如果 w <= k 那么答案应该比这个M更小或等 , 放到q1里面   

  如果 w>k    那么答案应该比M更大 , 减去当前查询已知有这么多[更大加过的表现],放到q2里面,查询更大答案。

代码Code:请结合上述注释食用

# include <bits/stdc++.h>
# define inf (0x7f7f7f7f)
using namespace std;
const int N=2e5+10;
struct rec{int l,r,k,id,type,val;}a[N<<1],q1[N<<1],q2[N<<1];
int ans[N<<1],n,m,tot;
# define lowbit(x) (x&(-x))
int c[N];
inline int read()
{
    int X=0,w=0; char c=0;
    while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
    while(c>='0'&&c<='9') X=(X<<3)+(X<<1)+(c^48),c=getchar();
    return w?-X:X;
}
inline void write(int x)
{
    if (x<0) { x=-x; putchar('-');}
    if (x>9) write(x/10);
    putchar('0'+x%10);
}
void update(int x,int y){for (;x<=n;x+=lowbit(x)) c[x]+=y;}
int query(int x){int ret=0;for (;x;x-=lowbit(x)) ret+=c[x];return ret;}
# undef lowbit
void solve(int ql,int qr,int L,int R)
{
    if (ql>qr) return;
    if (L==R) {
        for (int i=ql;i<=qr;i++)
         if (a[i].type==2) ans[a[i].id]=L;
        return;
    }
    int M=(L+R)>>1,t1=0,t2=0;
    for (int i=ql;i<=qr;i++)
     if (a[i].type==1) {
         if (a[i].val<=M) update(a[i].id,1),q1[++t1]=a[i];
         else q2[++t2]=a[i];
     } else if (a[i].type==2) {
         int num=query(a[i].r)-query(a[i].l-1);
         if (num>=a[i].k) q1[++t1]=a[i];
         else a[i].k-=num,q2[++t2]=a[i];
     }
     for (int i=1;i<=t1;i++)
      if (q1[i].type==1) update(q1[i].id,-1);
     for (int i=1;i<=t1;i++) a[ql+i-1]=q1[i];
     for (int i=1;i<=t2;i++) a[ql+t1+i-1]=q2[i];
     solve(ql,ql+t1-1,L,M);
     solve(ql+t1,qr,M+1,R);
}
int main()
{
    n=read();m=read();
    for (int i=1;i<=n;i++) {
        int t=read();
        a[++tot].id=tot;
        a[tot].type=1;
        a[tot].val=t;
    }
    for (int i=1;i<=m;i++) {
        a[++tot].type=2;a[tot].l=read();
        a[tot].r=read();a[tot].k=read();a[tot].id=tot;
    }
    solve(1,tot,-inf,inf);
    for (int i=n+1;i<=tot;i++) write(ans[i]),putchar('\n');
    return 0;
}
静态区间K大数 整体二分算法

题目2: P2617 Dynamic Rankings 

这个树状数组套可持久化线段树还记不记得,其实用整体二分是很简单实现的。

就是把删除操作拆分成2个操作,删(减去原来的)加(加上现在的)

我们把一定部分的代码做出了修改,主要是在树状数组加入(+y)和删除(-y)上

对于每一个2操作多了一个参数y表示是删除(-1)还是加上(1)

代码Code:请结合上述题目1和上述注释食用:

# include <bits/stdc++.h>
# define fp(i,s,t) for(int i=s;i<=t;i++)
using namespace std;
const int N=(1e5+5)*3;
const int inf=1e9;
struct rec{
    int x,y,k,op,id;
    //如果是1操作 值,加/减,无,操作编号,答案编号
    //如果是2操作 左边界,右边界,k小值,操作编号,数组中位置编号
}a[N],q1[N],q2[N];
int tmp[N],ans[N],n,m,tot;
# define lowbit(x) (x&(-x))
int c[N];
void update(int x,int y) { for (;x<=n;x+=lowbit(x)) c[x]+=y;}
int query(int x) {int ret=0;for (;x;x-=lowbit(x)) ret+=c[x];return ret;}
# undef lowbit
void solve(int ql,int qr,int L,int R)
{
    if (ql>qr) return;
    if (L==R) {
        fp(i,ql,qr) if (a[i].op==2) ans[a[i].id]=L;
        return;
    }
    int M=(L+R)>>1,t1=0,t2=0;
    fp(i,ql,qr)
     if (a[i].op==1) {
         if (a[i].x<=M) {
             update(a[i].id,a[i].y); //*加上a[i].y
             q1[++t1]=a[i];
        } else q2[++t2]=a[i];
     } else {
         int num=query(a[i].y)-query(a[i].x-1);
         if (num>=a[i].k) q1[++t1]=a[i];
         else a[i].k-=num,q2[++t2]=a[i];
     }
     for (int i=1;i<=t1;i++)
      if (q1[i].op==1) update(q1[i].id,-q1[i].y);//*加上-a[i].y还原
     fp(i,1,t1) a[ql+i-1]=q1[i];
     fp(i,1,t2) a[ql+t1+i-1]=q2[i];
     solve(ql,ql+t1-1,L,M);
     solve(ql+t1,qr,M+1,R);
}
int main()
{
    scanf("%d%d",&n,&m);
    fp(i,1,n) {
        scanf("%d",&tmp[i]);
        a[++tot]=(rec) {tmp[i],1,0,1,i};
    }
    int qes=0;
    fp(i,1,m) {
        char c=0;
        while (c!='Q'&&c!='C') c=getchar();
        if (c=='Q') {
            int l,r,k; scanf("%d%d%d",&l,&r,&k);
            a[++tot]=(rec) {l,r,k,2,++qes};
        } else {
            int pos,t; scanf("%d%d",&pos,&t);
            a[++tot]=(rec) {tmp[pos],-1,0,1,pos};//-1删除
            tmp[pos]=t;
            a[++tot]=(rec) {tmp[pos],1,0,1,pos}; //+1增加
        }
    }
    solve(1,tot,-inf,inf);
    fp(i,1,qes) printf("%d\n",ans[i]);
    return 0;
}
带修改区间K大数 整体二分算法

整体二分的复杂度的话,

离散化优化整体二分 $O(m {log_2}^2 n)$

不优化(也差不多) $O(m {log_2} n log_2 INF)$

 

posted @ 2019-02-23 21:28  ljc20020730  阅读(267)  评论(0编辑  收藏  举报