BZOJ 1878 【SDOI2009】HH的项链(离线+树状数组)

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1878


思路:参考了http://blog.csdn.net/njlcazl/article/details/8758443

题目大意:询问一个序列中区间[a,b]中不同的数有几个,无修改操作。
比较容易想到的是使用线段树套平衡树来解决,但是这道题需要有合并的操作,时间复杂度很高,不能接受。
并且我们可以知道这样的点的共同特点为上一个该颜色的点在查询区间的左侧,这样问题转化为求区间[a,b]中上一个同色点在[a,b]左侧的点的数目。
于是可以使用线段树套一个线性表的方法做,二分查找,在log时间内求出小区间的答案,这样就可以做到满分了。
具体来说就是:按区间右界排序,预处理出上一个同色点的位置,然后从前到后扫描,每次将上一个同色点的值加1,将当前位置下个位置的值减1,然后求当前区间的左界的前缀和就是答案了。
这样我们需要一个可以实现两种操作的数据结构:
1、将某个位置+1或-1
2、求前缀和
而实现这两种操作的最好的数据结构就是树状数组,所以我们就用一个树状数组维护就行了。
代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 200000 + 10;
const int maxm = 1000000 + 10;
struct ques
{
    int l,r;
    int pos;
}Seg[maxn];
int pre[maxn],col[maxn];
int sum[maxn],ans[maxn];
int last[maxm];
int n,m;
int cmp(const ques &a,const ques &b)
{
    return a.r < b.r;
}

void init()
{
    freopen("bzoj1878.in","r",stdin);
    freopen("bzoj1878.out","w",stdout);
}

inline int lowbit(int x)
{
    return x & -x;
}

void add(int x,int p)
{
    while(x <= n)
    {
        sum[x] += p;
        x += lowbit(x);
    }
}

int getsum(int x)
{
    int ret = 0;
    while(x > 0)
    {
        ret += sum[x];
        x -= lowbit(x);
    }
    return ret;
}

void readdata()
{
    scanf("%d",&n);
    for(int i = 1;i <= n;i++)
    {
        scanf("%d",&col[i]);
        pre[i] = last[col[i]];
        last[col[i]] = i;
    }
    scanf("%d",&m);
    for(int i = 1;i <= m;i++)
    {
        scanf("%d%d",&Seg[i].l,&Seg[i].r);
        Seg[i].pos = i;
    }
}

void solve()
{
    stable_sort(Seg + 1,Seg + m + 1,cmp);
    int now = 0;
    for(int i = 1;i <= m;i++)
    {
        while(now < Seg[i].r)
        {
            ++now;
            add(pre[now] + 1,1);
            if(now != n)add(now + 1,-1);
        }
        ans[Seg[i].pos] = getsum(Seg[i].l);
    }
    for(int i = 1;i <= m;i++)printf("%d\n",ans[i]);
}

int main()
{
    init();
    readdata();
    solve();
    return 0;
}

下面加一些自己的理解:

因为操作中只有询问没有更新,所以可以使用离线算法,对所有询问按右区间升序排序。

以该数字第一次在区间中出现的点代表所有的点。如果是第一次出现,那么该数字 在之前从未出现或上一次出现不再区间内。

记录每个位置i的数字的前一个相同数字出现的位置pre[i],没有前一个相同的pre[i]为0。
然后从前到后扫描询问,每次将上一个同值点的值加1,然后求当前区间的左界的前缀和就是答案了。将当前位置下个位置的值减1,这样做可以保证任意一个数字在任意一段区间中最多出现一次。
代码中注释位置及下一行+1的原因是树状数组的第一个元素只能从1开始,不能从0开始。

下面给出我的两段代码,其中第二段是将pre[]归算到1开始计数的,避免了树状数组处理时+1的问题,较第一段代码注释处有更改。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=1000010;
int n;

struct Node
{
    int id,x,y;
    bool operator < (const Node &b) const
    {
        return y<b.y;
    }
}ask[N];

int last[N],pre[N],ans[N];
int bit[N];

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

void Update (int k,int x)
{
    while (k<=n)
    {
        bit[k]+=x;
        k+=lowbit(k);
    }
}

int Getsum (int k)
{
    int sum=0;
    while (k>0)
    {
        sum+=bit[k];
        k-=lowbit(k);
    }
    return sum;
}

int main ()
{
    int i,q,temp;
    scanf("%d",&n);
    for (i=1;i<=n;++i)
    {
        scanf("%d",&temp);
        pre[i]=last[temp];
        last[temp]=i;
    }
    scanf("%d",&q);
    for (i=0;i<q;++i)
    {
        scanf("%d%d",&ask[i].x,&ask[i].y);
        ask[i].id=i;
    }
    sort(ask,ask+q);
    int now=0;
    for (i=0;i<q;i++)
    {
        while (now<ask[i].y)
        {
            ++now;
            Update (pre[now]+1,1);    //树状数组从1开始
            Update (now+1,-1);
        }
        ans[ask[i].id]=Getsum(ask[i].x);
    }
    for (i=0;i<q;i++)
        printf("%d\n",ans[i]);
    return 0;
}
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N=1000010;
int n;

struct Node
{
    int id,x,y;
    bool operator < (const Node &b) const
    {
        return y<b.y;
    }
}ask[N];

int last[N],pre[N],ans[N];
int bit[N];

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

void Update (int k,int x)
{
    while (k<=n+1)    //
    {
        bit[k]+=x;
        k+=lowbit(k);
    }
}

int Getsum (int k)
{
    int sum=0;
    while (k>0)
    {
        sum+=bit[k];
        k-=lowbit(k);
    }
    return sum;
}

int main ()
{
    int i,q,temp;
    scanf("%d",&n);
    for (i=1;i<=n;++i)
    {
        scanf("%d",&temp);
        pre[i]=last[temp]+1;  //
        last[temp]=i;
    }
    scanf("%d",&q);
    for (i=0;i<q;i++)
    {
        scanf("%d%d",&ask[i].x,&ask[i].y);
        ask[i].id=i;
    }
    sort(ask,ask+q);
    int now=1;        //
    for (i=0;i<q;i++)
    {
        while (now<=ask[i].y)
        {
            ++now;
            Update (pre[now-1],1);    //
            Update (now,-1);      //
        }
        ans[ask[i].id]=Getsum(ask[i].x);
    }
    for (i=0;i<q;i++)
        printf("%d\n",ans[i]);
    return 0;
}

感谢whyorwhnt与njlcazl大大

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值