简单树状数组+离线 HDU3874

HDU 3874:http://acm.hdu.edu.cn/showproblem.php?pid=3874

题目大意: 区间不重复数求和——N个数,无序,M次查询。  查询是,给一个区间,要求出这个区间内 所有数字的和。 前提是:数字相同不能重复相加。。。。。。

N 最大50000, 查询的次数M 最大 200000,每个数最大为1000000,和肯定要用long long

 

  看到众人都说此题简单,我非常为自己的愚蠢痛心疾首。这一题找错误样例就找了一百年啊一百年。以下我先弱弱的阐释一下自己的错误经历,若要看正确题解可以向后翻。

 

  刚开始是 英语不好,自以为读懂了题意,以为是 将给的数组排序以后,求区间的不重复数字和。 开开心心的排序以后,感到十分奇怪,这样还要用树状数组?用一个数组sum来保存前n项和(包括当前 i ),一个数组repeat保存前n项中 出现的重复的数字和(包括当前 i )。给定一个区间 [ L , R ],用小学生等级的容斥求 sum[R] - sum[L] - repeat[R] + repeat[L-1]  即可,简直就是O(1)的复杂度啊。欢欣雀跃过了样例就交了,毫无悬念的WA。

 

  痛定思痛,我摸着自己的头颅思考是错在哪了。看完一遍题意,哦题目没说要排序。呵呵,英语不行拉低智商。转念一想,无序的数组我这个方法也行的通啊。妙啊,(ノ`Д)ノ。于是改了一下,又交了已发,还是毫无悬念的WA。

 

  痛定思痛定思痛,我抠着自己残存不多的头发,找错误样例。试了好几次都没什么毛病。感到绝望。断断续续找了一下午,诶,发现一个简单的样例就过不了: 2 1 2

如果使用这个3个数              2 1 2

那么理论上的前n项和为       2 3 5

前n项重复的数字是              0 0 2

  选别的区间都没问题,但是如果选区间 【1,2】的话,(下标为0),则正确答案为3 ,可是按照  sum[R] - sum[L] - repeat[R] + repeat[L-1] 求出来就是 5-2-2+0=1 居然错了。

 

  可恶,花了这么多精力,还是错误的。至少知道自己的思路为什么错了。如果用这个方法,是有可能会将不包含重复数字,但是前面确实有重复数字的区间给算错。。。。

 

   看来还是自己蠢,还是用树状数组吧。

 

  看了大家的博客,正确的题解是: 将给定的区间保存下来,离线处理,将它们以右区间小的在前排序。然后顺序解决每个排好序的区间即可。解决办法是,每次都更新记录重复的数字的最后出现的位置,然后将前面出现的重复数字的位置,在树状数组中 更新为0 。如此一来,求区间和的时候,相同的数字就可以只计算一次。

 

产生疑问1,为什么是按照右区间排序,不是左区间?  ——因为按照右区间排序以后, 我们从下标1 开始更新数字出现的最后位置 操作 ,在更新到 右区间位置的时候, 这个查询就可以求出来了

产生疑问2 ,对于每一个区间N ,都要从头扫一遍来更新区间 ,若区间都覆盖贼大为N ,那么 M次 操作总时间 为 查询次数*(扫的次数+求和时间) =  M*(N+ logN) 肯定超时啊。—— 前一个区间,他们各个数字的最后出现位置的更新,对后面区间的更新操作已经重合了,不需要从头扫了。也就是说,扫的操作,就是N,所以总的复杂度是 MlogN +N

接下来就是映射了,用map也行自己写也行。

写完代码,怒交,居然TE了??难道是map的问题?不科学,看了别人代码也是用map怎么就我的TE呢?

把别人的代码一行一行扒下来惊醒的对照。。。。。。保存问题的数组开小了,不能用N的大小,要用M的大小,也就是N*4,尴尬,OJ居然给我TE报错。。。

心很痛,找了这么久BUG,此题真是收获良多。。。

 

下面就放一下代码吧,真是充满惆怅啊/(ㄒoㄒ)/

 

#include<bits/stdc++.h>
using namespace std;
#define inf 50009
#define INF 999999999
#define ll long long
#define loop(x,y,z) for(x=y;x<z;x++)
int n,a[inf];//输入数组
struct node
{
    int id,l,r;
    bool operator<(const node&j)const{return r<j.r;}
}ask[inf*4];  //询问的区间
ll ans[inf*4];//每次查询的答案
ll c[inf]; //树状数组
map<int,int>m;//映射数字最后出现位置
void init()
{
    memset(c,0,sizeof c);
    m.clear();
}
int lowbit(int i)
{
    return i&-i;
}
ll cal_sum(int i)
{
    ll sum=0;
    while(i>0){
        sum+=c[i];
        i-=lowbit(i);
    }
    return sum;
}
void add(int i,int type)    //type为0则抹除,为1则添加
{
    int t=type?a[i]:-a[i];
    while(i<=n){
        c[i]+=t;
        i+=lowbit(i);
    }
}
int main()
{
    int i,j,q,T;
    scanf("%d",&T);
    while(T--)
    {
        init();
        scanf("%d",&n);
        loop(i,1,n+1)scanf("%d",&a[i]);
        scanf("%d",&q);
        loop(i,1,q+1){ask[i].id=i;scanf("%d%d",&ask[i].l,&ask[i].r);}
        sort(ask+1,ask+q+1);
        j=1;
        loop(i,1,q+1)
        {
            while(j<=ask[i].r)
            {
                if(m[a[j]]==0){m[a[j]]=j;add(j,1);}
                else
                {
                    add(m[a[j]],0);
                    add(j,1);
                    m[a[j]]=j;
                }
                j++;
            }
            ans[ask[i].id]=cal_sum(ask[i].r)-cal_sum(ask[i].l-1);
        }
        loop(i,1,q+1)printf("%lld\n",ans[i]);
    }
    return 0;
}

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值