Different Integers题解

本文介绍了一种使用树状数组高效求解区间内不重复元素数量的方法。通过对输入序列进行预处理,利用树状数组维护区间信息,实现快速查询与更新。适用于处理大量查询请求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

输入:

3 2

1 2 1

1 2

1 3

4 1

1 2 3 4

1 3 

输出:

2

1

* 1 ≤ n, q ≤ 10 *5    1 ≤ ai ≤ n * 1 ≤ li, ri ≤ n

直接上代码

#include <iostream>
using namespace std;

struct Query
{
    int l, r, id;
};

bool operator < (const Query& u, const Query& v)
{
    return u.r < v.r;
}

int main()
{
    int n, q;
    while (cin>> n >> q) == 2) 
{
        vector<int> a(n), first(n, -1), last(n);

        int total = 0;
        for (int i = 0; i < n; ++ i) 
		{
            cin>>a[i];
            a[i] --, last[a[i]] = i;
            if (first[a[i]] == -1) 
			{
                total ++, first[a[i]] = i;
            }
        }
        vector<Query> queries;
        for (int i = 0, l, r; i < q; ++ i)
		{
            cin>>l>>r;
            queries.push_back(Query {l - 1, r - 1, i});
        }

        sort(queries.begin(), queries.end());

        vector<int> count(n), result(q);

        for (int i = 0, k = 0; i < n; ++ i)
		{
            while (k < q && queries[k].r == i)
			{
                int& ref = result[queries[k].id] = total;
                for (int j = queries[k].l; j < n; j += ~j & j + 1)
				{
                    ref -= count[j];
                }
                k ++;
            }
            if (last[a[i]] == i)
			{
                for (int j = first[a[i]] - 1; ~j; j -= ~j & j + 1)
				{
                    count[j] ++;
                }
            }
        }
        for (int i = 0; i < q; ++ i)
		{
            printf("%d\n", result[i]);
        }
    }
}

 基本思路为,先将数列保存在一个数组a[n]中,然后用一个结构体Query保存每个数对的left和right下标,还有他的id(为方便排序后输出)。Query重载了<运算符,方便在排序中,按right升序排序。因为起始从0开始,所以对所有数,l,r作减一处理。        在输入数组的时候,用first数组记录数第一次出现的下标,用last记录最后出现的下标。

在寻找区间内不重复个数时,在对a[n]的遍历中,不是去找该数是否重复,而是通过确定一个区域的未出现数的个数,并用在输入a[n]时得到的数的总个数(不重复)total减去该未出现数,即为在该区域出现的数的个数,即我们的目标。

如何确定一个区域的未出现数的个数count?

假设一个区间[ 0,R ] ,如果 first[ a[ i ] > R,那么说明该数在该区间不存在。如何遍历?

这里我们用树状数组count数组来维护对于[0 ,n]区间的count。

维护过程:当遍历下标为i时,判断:若 i == last[ a[ i ] ],即此时为a[ i ]这个数最后出现的下标。这时,我们再去将该数第一次出现时的位置以前的区间的未出现数的个数加一,因为该数在 first[ a [ i ]] 之前肯定没有出现过。

如何记录这个count,如果我们将每个count[ i ] + 1的话,那么我们要将first[a[ i ]]往前的所有count都 + 1,这样毫无疑问时间复杂度会很大,会超时。那么如何维护这个count?可以观察到,count的值都是由右往左叠加。这样,我们就可以用树状数组去维护。即 j -= ~j & (j + 1)  (或者 j & - j)。

当我们遍历到一个区间Query的 右节点时,我们就可以通过统计除该区间以外的未出现数,最后用total减去就可以得到出现数了。但是代码中,我们却是用 for (int j = queries[k].l; j < i; j += ~j & j + 1) 去统计。按照树状数组的原理,这样获得的count总值sum本应该是[ 0 ~ l]区间的未出现数。 但是,注意这个时候,我们仅仅遍历到了 i,或者说queries[k].r,即这个Query区间右边的数还没有被遍历,因此右边的数也并没有影响区间左边的区间的未出现数的个数!!如果将右边也遍历完,那么理应左边及右边的未出现数都会被更新完毕。但这样,我们将Query排序遍历的意义就没有了。因为这样,我们是可以将所有Query一遍遍历完毕的。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值