HDU 3333 Turing Tree(树状数组/主席树)

题意

给定一个长度为 \(n​\) 的序列,\(m​\) 个查询,每次查询区间 \([L,R]​\) 范围内不同元素的和。

\(1\leq T \leq 10\)

\(1 \leq n\leq 30000\)

\(1\leq m\leq 100000\)

思路

这道题没有强制在线,又没有修改,离线会比在线好想。

可以从第 \(1\) 个数到第 \(n\) 个数一次添加,并去除之前的相同元素,以此为顺序。就是说对于 \(m\) 个询问,按右端点进行排序,以此添加进每个数字并只保留最右端的数,借助 \(\text{map}\) 去重,区间和用树状数组维护。

假如要强制在线,该怎么办呢?

假如我们能力开下 \(n\) 个树状数组,就可以在线的查询了,可是 \(n\) 个树状数组肯定开不下,那动态开点线段树?可以,但是每次也是要把原来的线段树复制一遍,复杂度一累,时间过不去。
主席树的作用就体现出来了,回顾离线的写法,每次只会修改 \(1-2\) 个位置,那在前缀的基础上,保留原来的历史版本不就行了?这就是可持久化,详见代码。

代码

#include<bits/stdc++.h>
#define FOR(i,x,y) for(int i=(x),i##END=(y);i<=i##END;++i)
#define DOR(i,x,y) for(int i=(x),i##END=(y);i>=i##END;--i)
typedef long long LL;
using namespace std;
const int N=3e5+5;
const int NN=1e7+2e6+5;

struct ChairmanTree
{
    int lson[NN],rson[NN];LL sum[NN];
    int rt[N],tot;
    int &operator [](const int x){return rt[x];}
    void build()
    {
        memset(rt,0,sizeof(rt));
        sum[tot=0]=lson[0]=rson[0]=0;
    }
    void create(int &k){sum[++tot]=sum[k],lson[tot]=lson[k],rson[tot]=rson[k],k=tot;}
    void update(int &k,int x,int val,int l,int r)
    {
        create(k);
        if(l==r)
        {
            sum[k]+=val;
            return;
        }
        int mid=(l+r)>>1;
        if(x<=mid)update(lson[k],x,val,l,mid);
        else update(rson[k],x,val,mid+1,r);
        sum[k]=sum[lson[k]]+sum[rson[k]];
    }
    LL query(int k,int L,int R,int l,int r)
    {
        if(!k)return 0;
        if(L<=l&&r<=R)return sum[k];
        int mid=(l+r)>>1;
        if(R<=mid)return query(lson[k],L,R,l,mid);
        else if(L>mid)return query(rson[k],L,R,mid+1,r);
        else return query(lson[k],L,R,l,mid)+query(rson[k],L,R,mid+1,r);
    }
}CT;
map<int,int>mp;

int main()
{
    int T,n,m;
    scanf("%d",&T);
    while(T--)
    {
        mp.clear();
        scanf("%d",&n);
        CT.build();
        FOR(i,1,n)
        {
            int x;
            scanf("%d",&x);
            CT[i]=CT[i-1];
            if(mp[x])CT.update(CT[i],mp[x],-x,1,n);
            CT.update(CT[i],i,x,1,n);
            mp[x]=i;
        }
        scanf("%d",&m);
        while(m--)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            printf("%lld\n",CT.query(CT[y],x,y,1,n));
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/Paulliant/p/10144336.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值