黑白博弈 | 线段树(维护区间最小值&&最大值)

黑白博弈

  • 题目
  • 分析
  • 代码

题目

在这里插入图片描述
题目链接:黑白博弈

分析

首先我们通过读题:单点修改查询很容易发现这道题可以用线段树解决,既然决定了用线段树解决,那么我们需要确定以下几个要素:

1:使用线段树维护什么?

2:如何通过线段树维护的数据获得我们需要的答案?

Question1:

我们需要用利用线段树维护什么?

通过题目要求我们得知在进行查询操作时需要查询的数据是当前白块区间所有连续的白块数量,也就是说只要知道了该白块区间两端的黑块下标,我们就可以通过这两个黑块下标计算得出该区间的白块个数。

那么——如何通过查询得到目标白块所处区间两端的黑块下标呢?

这就是我们线段树要维护的数据了:该区间中黑块下标的最大值与最小值。(还有不懂的小伙伴请看下面的图示分析)

假设有长度为5的白块(初始状态全为白块)
我们将其(白块)最大值赋值为负无穷大(下面以INFF代替),最小值则赋值为正无穷大(下面以INFZ代替)前者为最大值,后者为最小值

1-5 INFF INFZ
1-3 INFF INFZ
4-5 INFF INFZ
1-2 INFF INFZ
3-3 INFF INFZ
4-4 INFF INFZ
5-5 INFF INFZ
1-1 INFF INFZ
2-2 INFF INFZ

当我们第一次将2和4涂黑时,我们将位置为2和4的叶子节点中的最大值和最小值修改为其下标,同时更新区间最大值与最小值:

1-5 4 2
1-3 2 2
4-5 4 4
1-2 2 2
3-3 INFF INFZ
4-4 4 4
5-5 INFF INFZ
1-1 INFF INFZ
2-2 2 2

我们想要查询3这个白块所在区间的所有白块数量时,我们需要做的就是查询这个区间左边界与右边界黑块的下标,即该白块左边黑块下标的最大值以及该白块右边黑块下标的最小值,这就是我们的线段树要维护区间的最大值以及最小值的原因了。

此时我们可以知道位置为3的白块左边黑块下标最大值为2,右边黑块下标最小值为4,那么我们得到的答案就是4-2-1=1。

Question2:

我们如何利用线段树维护的数据获得我们想要的答案呢?

这就很简单了,在问题一的分析中我们能注意到,假设我们想求位置为3的白块所在区间所有白块的数量,我们能够通过查询得到左边黑块下标的最大值max以及右边黑块下标的最小值min,于是乎,我们需要的答案就是 max-min-1

此外我们还需要注意几种特殊情况:
1:左边没有黑块或者右边没有黑块
2:左右都没有黑块

避开这些坑之后代码就很好写了
我就是被这些坑堵住了好几个小时

代码

#include<cstdio>//黑白博弈
#include<string.h>
#include<algorithm>
#include<iostream>
#include<math.h>
using namespace std;
const int INF=0x3f3f3f3f;//定义无穷大
const int maxn=1e5+7;
struct Node{//线段树节点
    int l,r;
    int maxx;
    int minn;
} tr[maxn*4];
void build(int d,int l,int r){//建树//维护区间最大值以及最小值
    tr[d].l=l;
    tr[d].r=r;
    if(l==r){//初始化为白块//最大值赋值为-INF,最小值赋值为INF
        tr[d].maxx=-INF;
        tr[d].minn=INF;
        return ;
    }
    int mid=(l+r)/2,lc=d*2,rc=d*2+1;
    build(lc,l,mid);
    build(rc,mid+1,r);
    tr[d].maxx=max(tr[lc].maxx,tr[rc].maxx);//回溯更新最大值以及最小值
    tr[d].minn=min(tr[lc].minn,tr[rc].minn);
}
void update(int d,int l,int r,int x){//单点修改
    int mid=(tr[d].l+tr[d].r)/2,lc=d*2,rc=d*2+1;
    if(tr[d].l==l&&tr[d].r==r&&x!=INF){//将白块涂黑
        tr[d].minn=x;
        tr[d].maxx=x;
        return ;
    }else if(tr[d].l==l&&tr[d].r==r&&x==INF){//将黑块涂白
        tr[d].minn=x;
        tr[d].maxx=-x;
        return ;
    }
    if(l>mid){
        update(rc,l,r,x);
    }else if(r<=mid){
        update(lc,l,r,x);
    }
    tr[d].maxx=max(tr[lc].maxx,tr[rc].maxx);//回溯更新
    tr[d].minn=min(tr[lc].minn,tr[rc].minn);
}
//标准的区间查询模板//不知道能不能将最大值与最小值的查询合并呢
int queryz(int d,int l,int r){//左区间查询最大值
    if(tr[d].l>=l&&tr[d].r<=r){
        return tr[d].maxx;
    }
    int mid=(tr[d].l+tr[d].r)/2,lc=d*2,rc=d*2+1;
    if(l>mid){
        return queryz(rc,l,r);
    }else if(r<=mid){
        return queryz(lc,l,r);
    }else{
        return max(queryz(lc,l,mid),queryz(rc,mid+1,r));
    }
}
int queryy(int d,int l,int r){//右区间查询最小值
    if(tr[d].l>=l&&tr[d].r<=r){
        return tr[d].minn;
    }
    int mid=(tr[d].l+tr[d].r)/2,lc=d*2,rc=d*2+1;
    if(l>mid){
        return queryy(rc,l,r);
    }else if(r<=mid){
        return queryy(lc,l,r);
    }else{
        return min(queryy(lc,l,mid),queryy(rc,mid+1,r));
    }
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    build(1,1,n);
    while(m--){
        int a;
        char ch;
        scanf(" %c%d",&ch,&a);
        if(ch=='B'){
            update(1,a,a,a);//变黑
        }else if(ch=='W'){
            update(1,a,a,INF);//变白
        }else{
            if(queryz(1,1,a)==queryy(1,a,n))//如果左最大值与右最小值相等则证明为黑块,输出0;
                printf("0\n");
            else
            {
                if(queryy(1,a,n)==INF&&queryz(1,1,a)!=-INF)//如果右边没有黑块
                    printf("%d\n",n-queryz(1,1,a));
                else if(queryz(1,1,a)==-INF&&queryy(1,a,n)!=INF)//如果左边没有黑块
                    printf("%d\n",queryy(1,a,n)-1);
                else if(queryz(1,1,a)==-INF&&queryy(1,a,n)==INF)//如果两边都没有黑块(相当于全都为白块)
                    printf("%d\n",n);
                else//如果左右两边都有黑块(正常求)
                    printf("%d\n",queryy(1,a,n)-queryz(1,1,a)-1);
            }
        }
    }
    return 0;
}

That’s All

Thanks for watching!

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值