树状数组(三)

来源:互联网 发布:日本网络电视直播apk 编辑:程序博客网 时间:2024/05/16 03:06

题目链接: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 0x3f3f3f3fusing 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、树状数组是辅助工具,用来快速求和的!

0 0
原创粉丝点击