树状数组(三)

本文介绍Codeforces220B题目的解题思路,利用离线思想结合树状数组实现高效查询,详细解释了解题过程及AC代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:codeforces 220B

题意分析:有n个数,m次询问,一开始给你这n个数ai,每一次询问的时候给你一个区间,求这个区间里面满足“数值为x,出现了x次”的条件的数有几个!(1 ≤ n, m ≤ 100000) 从题述上看,这题的难点应该是区间的频繁访问,于是可以想到线段树或者树状数组

解题思路:
即便是线段树或者树状数组这种高端数据结构,有时候也是需要一些辅助算法的,在这道题里面,辅助算法(思想)就是离线思想!
离线思想:其实很简单的一种思想,就是回应查询的时候不是一次查询一次回复,而是多次查询一次回复。那么这种思想究竟对我们解题有什么帮助呢?
我们来考虑用树状数组怎么解决这道题,首先,找到的数x,一定满足 x ≤ n。假设我们要查询一个区间[ l , r ],那么是不是如果我们事先知道了区间上点 i 到 r 这个区间有多少个数,就会方便很多?好像是会方便很多,但是好像会重复计算,那么我们就考虑这个点 i 在区间内是否是满足要求的数 x 。好像有点头绪了~
首先定义一个数组x,还有bit数组,是x的树状数组,另外我们需要一个vector(动态数组),vec[i]记录数字 i 在什么位置出现过:
从头开始遍历数组a,如果ai ≤ n则将其push到vec[ai]中,此时我们来检测一下vec[ai]的大小:
1)如果size ≥ ai,说明出现了一个满足题意的区间了,我们需要标记!于是,执行操作:x[ vec[ ai ][ size-ai ] ] = 1,表示从vec[ai][size-ai]那里到当前的位置 i,ai这个数满足条件。
2)如果size ≥ ai+1,说明我们之前已经将vec[ai][size-ai-1]这个位置标记了,这个点到当前位置 i 明显是有ai+1个ai的,我们需要订正它——x[ vec[ ai ][ size-ai ] ] = -1。(之所以是-1,是因为在[ vec[ai][size-ai-1] , i ]这个区间中,ai不是满足条件的数x了!)
3)如果size ≥ ai+2,我们又要把刚刚订正的地方修正回来——x[ vec[ ai ][ size-ai ] ] = 0
因为,我们每次考虑问题都是以当前的 位置 i 为右界,那么为了节省时间,我们就应该对查询从左到右处理,在一切开始之前对查询按右边界进行排序。这就是所谓的离线处理。
每一次处理完一个数 i 之后,看看它是不是查询的右边界,是的话就用树状数组求和一下。
然后更新信息部分:

int tmp=a[i],len=vec[tmp].size()+1;
vec[tmp].push_back(i);
if(len>=tmp)
{
    add(vec[tmp][len-tmp],1);
    if(len>tmp)
        add(vec[tmp][len-tmp-1],-2);
    if(len>tmp+1)
        add(vec[tmp][len-tmp-2],1);
}

最后,按查询给出的顺序输出查询结果就行了!

AC代码:

#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define INF 0x3f3f3f3f

using namespace std;

int n,m,a[100005],bit[100005],ans[100005];
vector<int> vec[100005];

struct node{
    int l,r,index;
}q[100005];

bool cmp(node x,node y){
    return x.r<y.r;
}

int lowbit(int num){
    return num&(-num);
}

int sum(int index)
{
    int res=0;
    for(int i=index;i>0;i-=lowbit(i))
        res+=bit[i];
    return res;
}

void add(int index,int delta){
    for(int i=index;i<=n;i+=lowbit(i))
        bit[i]+=delta;
}

int main()
{
    while(~scanf("%d %d",&n,&m))
    {
        memset(bit,0,sizeof(bit));
        memset(vec,0,sizeof(vec));
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);

        for(int i=0;i<m;i++)
        {
            scanf("%d %d",&q[i].l,&q[i].r);
            q[i].index=i;
        }
        sort(q,q+m,cmp);

        int j=0;
        for(int i=1;i<=n;i++) 
        {
            if(a[i]<=n)
            {
                int tmp=a[i],len=vec[tmp].size()+1;
                vec[tmp].push_back(i);
                if(len>=tmp)
                {
                    add(vec[tmp][len-tmp],1);
                    if(len>tmp)
                        add(vec[tmp][len-tmp-1],-2);
                    if(len>tmp+1)
                        add(vec[tmp][len-tmp-2],1);
                }
            }
            while(j<m&&q[j].r==i)
            {
                ans[q[j].index]=sum(q[j].r)-sum(q[j].l-1);
                j++;
            }
        }

        for(int i=0;i<m;i++)
            printf("%d\n",ans[i]);
    }   

    return 0;
}

总结:
1、离线思想,灵活运用可以提高算法复杂度;
2、树状数组是辅助工具,用来快速求和的!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值