D-query(主席树)

Given a sequence of n numbers a1, a2, …, an and a number of d-queries. A d-query is a pair (i, j) (1 ≤ i ≤ j ≤ n). For each d-query (i, j), you have to return the number of distinct elements in the subsequence ai, ai+1, …, aj.

  • 题意:给你n个数,q个询问。每个询问,从l到r这段区间一共出现过多少个不重复的数字。
  • 刚开始也看不出来用主席树怎么做,因为刚开始用主席树,只会板子,这种变形不太会。也是看了题解才明白。我们这里用主席树做区间查询的时候,可以不用后来版本的树减去前面版本的树。因为只要统计出现一次的数字的个数嘛,所以我们在后面更新的时候需要在新建分枝的时候将原来这个数字出现过的位置的权值减去,加在后面出现的新节点上,就是在建树过程中不断地将这个出现过数字的位置在新树中的位置往后面移动,移动到最后一次出现的位置。我们在查询的时候只要查询以右端点为根的主席树就可以了。
    (我开始害怕漏点,但是多虑了,多想一下就可以了,下面是笔者自己的一点理解,可能描述不清,但因为这个就是有点抽象不好描述,见谅。因为我们查找区间,如果查到这棵树的话,说明区间右端点一定是这个树,那么一个点在前面出现过,我们在这个树里面只记录了在这个区间最后面的位置,那么可以这么想,固定右端点,从右端点往左遍历左端点,只要这个数字出现过,那么左端点更靠左形成的区间里面一定就包括了这个点,因为右端点固定,左端点越靠左,那么区间越大,既然右面的子区间都已经将这个数包含了,那么前面这个数字就算没有记录也没关系了,如果查找右端点在这个左边的前面区间,又换成新的树了,所以一定不会有漏点的情况。如果还是不明白可以结合代码多思考一下主席树的本质:多颗权值线段树的组合,各个线段树之间无影响,就明白了。)
    当然还有其他做法,可以莫队做,这里先不写了,自己这个菜鸡也不会。
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 3e4+5;
int root[maxn], number[maxn], last[1000005];
int cnt;
struct node
{
    int left, right;
    int sum;
}T[maxn*20];

void update(int left, int right, int& current, int source, int pos, int val)
{
    current = ++cnt;
    T[current] = T[source];
    if(left == right)
    {
        T[current].sum += val;
        return;
    }
    int mid = (left+right)>>1;
    if(pos <= mid)  update(left, mid, T[current].left, T[source].left, pos, val);
    else update(mid+1, right, T[current].right, T[source].right, pos, val);
    T[current].sum = T[T[current].left].sum+T[T[current].right].sum;
}

int query(int left, int right, int l, int r, int cur)
{
    //printf("(%d, %d), (%d, %d)\n", left, right, l, r);
    if(l <= left && right <= r) return T[cur].sum;
    int mid = (left+right)>>1;
    if(r <= mid)    return query(left, mid, l, r, T[cur].left);
    else if(l > mid) return query(mid+1, right, l, r, T[cur].right);
    else return query(left, mid, l, r, T[cur].left)+query(mid+1, right, l, r, T[cur].right);
}

int main()
{
    int n, q;
    cin >> n;
    for(int i = 1; i <= n; i++) scanf("%d", &number[i]);
    for(int i = 1; i <= n; i++)
    {
        if(last[number[i]])
        {
            int temp;
            update(1, n, temp, root[i-1], last[number[i]], -1);
            update(1, n, root[i], temp, i, 1);
        }
        else    update(1, n, root[i], root[i-1], i, 1);
        //cout << root[i] << endl;
        last[number[i]] = i;
    }
    scanf("%d", &q);
    while(q--)
    {
        int x, y;
        scanf("%d%d", &x, &y);
        printf("%d\n", query(1, n, x, y, root[y]));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值