(带讲解)bzoj2738 整体二分

个人理解,整体二分是有模板的

题意:给出n*n的矩阵,求出子矩阵的k小值。

方法1:二维主席树,把询问分成4个,线段树加加减减然后查询即可

方法2(正解):整体二分

大体思路:二分一个答案mid,然后把所有小于等于mid的数使用二维树状数组维护,然后判断询问集合中子矩阵中元素个数是否小于等于k,如果是,则mid也许可以更小,否则,mid只能变大。


二分细节:

依旧按照普通二分的思路,在每次元素个数>=k是记录这个询问的答案,保证这是目前的最优解。

继续二分时左边左端点l,右端点mid-1,右边左端点mid+1,右端点r即可。

由于整体二分的函数有四个参数,所以返回条件是x>y||l>r,依据二分模板不用考虑l==r。

教训:记得初始化cnt。

二分部分代码

void binary(int x,int y,int l,int r)
{
    if (x>y||l>r) return;
    cnt=0;
    int mid=(l+r)/2;
    while (a[t].v<mid) t++,add(t,1);
    while (a[t].v>mid) add(t,-1),t--;
    while (a[t+1].v==mid) t++,add(t,1);
    for (i=x;i<=y;i++)
    {
        if(query(id[i])>=q[id[i]].k)
            f[id[i]]=1,cnt++,ans[id[i]]=mid;
        else f[id[i]]=0;
    }
    int l1=x,l2=x+cnt;
    for (i=x;i<=y;i++)
    if (f[id[i]]) tmp[l1++]=id[i];
    else tmp[l2++]=id[i];
    for (i=x;i<=y;i++)
    id[i]=tmp[i];
    binary(x,l1-1,l,mid-1);
    binary(l1,l2-1,mid+1,r);
}

如何快速地把矩阵中所有小于等于mid的数都加入?

记录矩阵时不用二维数组,用结构体并记录x,y,v,然后按照v排序。

设指针t表示当前二维树状数组中加入的是小于等于mid的数,让指针变成mid就可以快速修改。

怎么把t变成mid?

while (a[t].v<mid) t++,add(t,1);
while (a[t].v>mid) add(t,-1),t--;
while (a[t+1].v==mid) t++,add(t,1);

一定要有最后一行来避免多个等于mid的数没有加入树状数组。


附整体二分模板,视具体题目有所改动

void binary(int x,int y,int l,int r)
{
    if (x>y||l>r) return;
    int mid=(l+r)/2;
    //处理相关数据结构(分三种情况讨论)
    //分割点集
    //维护id[]
    binary(x,l1-1,l,mid-1);
    binary(l1,l2-1,mid+1,r);
}

代码


#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int i,j,n,c[510][510],id[60010],cnt,f[60010],tmp[60010],m,t,maxx,ans[60010],tot;
struct node
{
    int x1,x2,y1,y2,k;
}q[60010];
struct node1
{
    int x,y,v;
}a[250010];
bool cmp(node1 x,node1 y)
{
    return x.v<y.v;
}
void add(int x,int delta)
{
    for (i=a[x].x;i<=n;i+=(i&-i))
        for (j=a[x].y;j<=n;j+=(j&-j))
        c[i][j]+=delta;
}
int ask(int x,int y)
{
    int ret=0;
    for (int k=x;k;k-=(k&-k))
        for (j=y;j;j-=(j&-j))
        ret+=c[k][j];
        return ret;
}
int query(int x)
{
    return ask(q[x].x2,q[x].y2)-ask(q[x].x1-1,q[x].y2)-ask(q[x].x2,q[x].y1-1)+ask(q[x].x1-1,q[x].y1-1);
}
void binary(int x,int y,int l,int r)
{
    if (x>y||l>r) return;
    cnt=0;
    int mid=(l+r)/2;
    while (a[t].v<mid) t++,add(t,1);
    while (a[t].v>mid) add(t,-1),t--;
    while (a[t+1].v==mid) t++,add(t,1);
    for (i=x;i<=y;i++)
    {
        if(query(id[i])>=q[id[i]].k)
            f[id[i]]=1,cnt++,ans[id[i]]=mid;
        else f[id[i]]=0;
    }
    int l1=x,l2=x+cnt;
    for (i=x;i<=y;i++)
    if (f[id[i]]) tmp[l1++]=id[i];
    else tmp[l2++]=id[i];
    for (i=x;i<=y;i++)
    id[i]=tmp[i];
    binary(x,l1-1,l,mid-1);
    binary(l1,l2-1,mid+1,r);
}
int main()
{
    scanf("%d %d",&n,&m);
    for (i=1;i<=n;i++)
        for (j=1;j<=n;j++)
        {
            scanf("%d",&a[++tot].v);
            a[tot].x=i;
            a[tot].y=j;
            maxx=max(maxx,a[tot].v);
        }
    sort(a+1,a+1+tot,cmp);
    for (i=1;i<=m;i++)
    scanf("%d %d %d %d %d",&q[i].x1,&q[i].y1,&q[i].x2,&q[i].y2,&q[i].k),id[i]=i;
    binary(1,m,0,maxx);
    for (i=1;i<=m;i++)
    {
        printf("%d\n",ans[i]);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值