VOJ - Turing Tree (莫队/离线线段树)

Turing Tree

题目链接: Turing Tree HDU - 3333

题意

给你一N个数字,有Q个询问,每次询问一个区间,求这个区间中不同的数字之和 (N < 3e4,Q < 1e5)


思路

首先,最简单想到的肯定是暴力算法,但是暴力算法一定会T,复杂度为O(N*Q)

所以我们要想办法去优化一下,看到了Q次询问,和每次询问一个区间,那么莫队算法就呼之欲出了。

莫队算法的复杂度为O(n^(3/2))基本是可以过的,离线之王。

那么如何处理呢,最最重要的就是处理add和def中的算法,当add时,我们肯定希望res能够加上这个坐标的值,但是怎么确定这个值有没有出现过呢,我们用一个num[]数组来储存这个数字出现的次数,最好能有一个合适的下标,来代表这个数据,因为数据浮动范围过大,每个数据都是1~1e9,所以我们需要离散化,来优化一下下标,这里用了map来作为离散化工具,给每个数据附上一个独一无二的ID,用aim[]数组来储存一下,每个坐标所对应的ID即可。

map(int,int) mp; //将读入的数字找到他的ID
int aim[MAXN]; //coor -> ID
int num[MAXN]; //ID ->counts
int cnt; //give ID


代码一

#include <bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = (int)j;i <= (int)k;i ++)
#define per(i,j,k) for(int i = (int)j;i >= (int)k;i --)
#define mmm(a,b) memset(a,b,sizeof(a))
#define pb push_back

typedef double db;
typedef long long ll;
const int MAXN = (int)1e5+7;
const int INF = (int)0x3f3f3f3f;

int teams; //每组的数量
struct Mo {
    int l,r,id;
    Mo (int l = 0,int r = 0,int id = 0):l(l),r(r),id(id){}
}e[MAXN];
bool cmp(const Mo&a,const Mo&b){
    if (a.l / teams == b.l / teams)
        return a.r < b.r;
    return a.l/teams < b.l/teams;
}

map<int,int> mp; //将读入的数字找到他的ID
int aim[MAXN]; //coor -> ID
int num[MAXN]; //ID   ->counts
int cnt;       //give ID
int N,Q;
int A[MAXN];
ll ans[MAXN];
ll res;

void add(int p){
    int id = aim[p];
    if(num[id] == 0) res += (ll)A[p];
    num[id] ++;
}
void def(int p){
    int id = aim[p];
    if(num[id] == 1) res -= (ll)A[p];
    num[id] --;
}

void init(){
    res = 0;
    cnt = 0;
    mmm(num,0);
    mp.clear();
}

int main()
{
    int T;
    scanf("%d",&T);
    while (T --){
        init();
        scanf("%d",&N);
        teams = sqrt(N); // teams不要忘
        rep(i,1,N) {
            scanf("%d",&A[i]);
            if (mp[A[i]] == 0){
                mp[A[i]] = ++cnt;
            }
            aim[i] = mp[A[i]];
        }
        scanf("%d",&Q);
        rep(i,1,Q) {
            scanf("%d %d",&e[i].l,&e[i].r);
            e[i].id = i;
        }
        sort(e+1,e+1+Q,cmp);
        int pl = 5,pr = 4;
        rep(i,1,Q) {
            while (pr < e[i].r) add(++pr);
            while (pl > e[i].l) add(--pl);
            while (pr > e[i].r) def(pr--);
            while (pl < e[i].l) def(pl++);
            ans[e[i].id] = res;
        }
        rep(i,1,Q) {
            printf("%lld\n",ans[i]);
        }
    }
}

思路二

采用离线线段树的手法,先将询问储存起来,对右节点进行排序,记录一个pos点,每次向着当前询问的右节点移动。然后用mp来映射每个值出现的最后一个位置。这个算法的核心思想就始终维护一个当前节点l到r的值每个都出现,并且都出现一次,再利用线段树的高效求区间和来求出答案,复杂度O(nlogn)

大神解读

这种线段树只能离线来写,离线的方法是按照查询区间的右端点来排序,然后这道题目的数据范围较大需要离散化简单处理一下,然后对于输入的每个点来说,顺序走下去,然后如果当前点之前出现过,便将之前的删除然后把现在的添加线段树中,为什么这么可以,看了网上神犇一句话,那就是对于要查询的区间,它的右端点固定后,那么重复的数字便是右面开始最后一次出现的 。


代码二

#include <bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = (int)j;i <= (int)k;i ++)
#define per(i,j,k) for(int i = (int)j;i >= (int)k;i --)
#define mmm(a,b) memset(a,b,sizeof(a))
#define pb push_back

#define lson rt<<1
#define rson rt<<1|1

typedef double db;
typedef long long ll;
const int MAXQ = (int)1e5+7;
const int MAXV = (int)3e4+7;
const int INF = (int)0x3f3f3f3f;

struct Node {
    int id,l,r;
    Node(int id = 0,int l = 0,int r = 0):id(id),l(l),r(r){}
}e[MAXQ];

bool cmp(const Node &a,const Node &b){
    if (a.r == b.r)
        return a.l < b.l;
    return a.r < b.r;
}

ll sum[MAXV<<2];   //线段树求sum
ll ans[MAXQ];      //ans stored
int num[MAXV];      //id  -> val
map<int,int> mp;    //val -> count
int N,Q;

void PushUp(int rt) {sum[rt] = sum[lson] + sum[rson];}
void Update(int L,int l,int r,int V,int rt){
    if (l == r){
        sum[rt] = V;
        return;
    }
    int m = l+r>>1;
    if(L <= m) Update(L,l,m,V,lson);
    else       Update(L,m+1,r,V,rson);
    PushUp(rt);
}
ll Query(int L,int R,int l,int r,int rt){
    if (L <= l && r <= R){
        return sum[rt];
    }
    int m = l+r>>1;
    ll ans = 0;
    if (L <= m) ans += (ll)Query(L,R,l,m,lson);
    if (R >  m) ans += (ll)Query(L,R,m+1,r,rson);
    return ans;
}

void init(){
    mmm(sum,0);
    mp.clear();
}
int main()
{
    int T;
    scanf("%d",&T);
    while (T --){
        init();
        scanf("%d",&N);
        rep(i,1,N) scanf("%d",&num[i]);
        scanf("%d",&Q);
        rep(i,1,Q) {
            int l,r;
            scanf("%d %d",&l,&r);
            e[i] = Node(i,l,r);
        }
        sort(e+1,e+1+Q,cmp);
        int pos = 0;
        rep(i,1,Q){
            while (pos < e[i].r) {
                pos ++; //每次移动指针到询问的右边界
                int pre = mp[num[pos]];
                if (pre != 0){
                    Update(pre,1,N,0,1); //将前一次出现的位置清零
                }
                mp[num[pos]] = pos;
                Update(pos,1,N,num[pos],1);
            }
            ans[e[i].id] = Query(e[i].l,e[i].r,1,N,1);
        }
        rep(i,1,Q) printf("%lld\n",ans[i]);
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值