离散化+树状数组

先给道题先:求区间内不同个数。

题目链接:https://www.spoj.com/problems/DQUERY/en/

DQUERY - D-query

#sorting #tree

 

EnglishVietnamese

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.

Input

  • Line 1: n (1 ≤ n ≤ 30000).
  • Line 2: n numbers a1, a2, ..., an (1 ≤ ai ≤ 106).
  • Line 3: q (1 ≤ q ≤ 200000), the number of d-queries.
  • In the next q lines, each line contains 2 numbers i, j representing a d-query (1 ≤ i ≤ j ≤ n).

Output

  • For each d-query (i, j), print the number of distinct elements in the subsequence ai, ai+1, ..., aj in a single line.

     

Example

Input
5
1 1 2 1 3
3
1 5
2 4
3 5

Output
3
2
3 

 

想做这题,首先得会树状数组,这里有个链接:

https://blog.youkuaiyun.com/ljd201724114126/article/details/81123722

 

题意:给一串序列a[n],求序列  a[i] 到 a[j] (i<=j) 中不同数字的个数。 

解法思路:

离线:就在把所有询问先存贮起来,预处理之后再一个一个操作。

首先先按 r(右边) 值从小到大排序,树状数组 tree[i] 表示 从1->i 中不同数字的个数有多少个。

那么求a[i].....a[j] 中的不同个数 即为 tree[j] - tree[i-1]

问题来了,怎么用树状数组进行维护呢?

按排序好的,从位置1开始,一直用树状数组更新,只要之前还没出现过,就在这位置上+1,要在以前出现过,就在以前出现的位置-1,而在当前的位置 +1

举个例子:6 7 8 6 10

求位置1->3 和位置4->5

首先排好序,接着从位置1开始,一直到位置3,之前都没出现过重复的数字,就+1好了。

到了位置4,a[4]=a[1]=6, 之前出现过重复的数字,那么先在位置1 减1(用树状数组来更新),然后在位置4 加1

最后到达位置5,之前没出现过重复的数字,直接在位置5加1就好了。

这样子就无论我要查询的是 [4,5],还是[1,5] 最终 值6也只是被算了一次。

 

贴下代码:

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

const int maxn=100010;
int num[maxn]; ///存储数列
int tree[maxn]; ///树状数组
int book[maxn];
int out[maxn];  ///存储结果
int N;

struct node
{
    int l,r;
    int pos;
}ask[maxn]; ///存储询问数组

bool cmp(node x,node y) ///按 r 从小到大排序
{
    return x.r<y.r;
}

int lowbit(int n) ///树状数组
{
    return n&(-n);
}

void add(int n,int v) ///更新值
{
    while(n<=N)
    {
        tree[n]+=v;
        n+=lowbit(n);
    }
    return;
}

int sum(int n) ///树状数组求前n个结果
{
    int result=0;
    while(n)
    {
        result+=tree[n];
        n-=lowbit(n);
    }
    return result;
}
int main()
{
    int Q;
    while(~scanf("%d%d",&N,&Q))
    {
        memset(book,0,sizeof(book));
        memset(tree,0,sizeof(book));
        memset(out,0,sizeof(out));

        for(int i=1;i<=N;i++)
            scanf("%d",&num[i]);


        for(int i=1;i<=Q;i++){
            scanf("%d%d",&ask[i].l,&ask[i].r);
            ask[i].pos=i; ///因为下面要排序,为了记忆,故要标记下是第几个
        }

        sort(ask+1,ask+1+Q,cmp);

        int temp=1;///看似二重循环,其实也只是从小到大扫了一遍
        ///因为值r已经排好序了,故j是从1开始,一直要最大的r。
        for(int i=1;i<=Q;i++)
        {
            for(int j=temp;j<=ask[i].r;j++)
            {
                if(book[num[j]]) ///之前有出现过,在之前的位置减1
                {
                    add(book[num[j]],-1);

                }
                add(j,1); /// 在这个位置加1 ,为什么加的是1呢,因为我们算的是不同数字的个数,
                ///不是值的和,跟刚开始入门树状数组中求的和的有些不同的。
                book[num[j]]=j;///用book数组存储值num[j] 的位置
            }
            temp=ask[i].r+1; ///表示下次开始的位置
            out[ask[i].pos]=sum(ask[i].r)-sum(ask[i].l-1);///用out数组存储每组询问的结果

        }

        for(int i=1;i<=Q;i++) ///输出结果
            printf("%d\n",out[i]);


    }
    return  0;
}

 

再来多一道非常类似的。

牛客网暑期ACM多校训练营(第一场)J-Different Integers

https://www.nowcoder.com/acm/contest/139/J

链接:https://www.nowcoder.com/acm/contest/139/J
来源:牛客网
 

题目描述

Given a sequence of integers a1, a2, ..., an and q pairs of integers (l1, r1), (l2, r2), ..., (lq, rq), find count(l1, r1), count(l2, r2), ..., count(lq, rq) where count(i, j) is the number of different integers among a1, a2, ..., ai, aj, aj + 1, ..., an.

输入描述:

The input consists of several test cases and is terminated by end-of-file.
The first line of each test cases contains two integers n and q.
The second line contains n integers a1, a2, ..., an.
The i-th of the following q lines contains two integers li and ri.

输出描述:

For each test case, print q integers which denote the result.

 

示例1

输入

复制

3 2
1 2 1
1 2
1 3
4 1
1 2 3 4
1 3

输出

复制

2
1
3

备注:

* 1 ≤ n, q ≤ 105
* 1 ≤ ai ≤ n
* 1 ≤ li, ri ≤ n
* The number of test cases does not exceed 10.

题意:给 l,r,求区间 [1,l ] 和区间 [ r , n ] 不同数字有多少个

解法跟上文差不多,把数组开大两倍,a1,a2, a3 ....an a1 a2 a3 ... an ,

那么即是求区间 [r,n+l ] 不同数字的个数。

修改下代码就行了。

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

const int maxn=200010;
int num[maxn]; ///存储数列
int tree[maxn]; ///树状数组
int book[maxn];
int out[maxn];  ///存储结果
int N;

struct node
{
    int l,r;
    int pos;
}ask[maxn]; ///存储询问数组

bool cmp(node x,node y) ///按 r 从小到大排序
{
    return x.r<y.r;
}

int lowbit(int n) ///树状数组
{
    return n&(-n);
}

void add(int n,int v)
{
    while(n<=N)
    {
        tree[n]+=v;
        n+=lowbit(n);
    }
    return;
}

int sum(int n)
{
    int result=0;
    while(n)
    {
        result+=tree[n];
        n-=lowbit(n);
    }
    return result;
}
int main()
{
    int Q;
    while(~scanf("%d%d",&N,&Q))
    {
        memset(book,0,sizeof(book));
        memset(tree,0,sizeof(book));
        memset(out,0,sizeof(out));

        for(int i=1;i<=N;i++){
            scanf("%d",&num[i]);
            num[i+N]=num[i];
        }
        N<<=1;


        for(int i=1;i<=Q;i++){
            scanf("%d%d",&ask[i].l,&ask[i].r);
            ask[i].l+=(N/2);
            swap(ask[i].l,ask[i].r);
            ask[i].pos=i;
        }

        sort(ask+1,ask+1+Q,cmp);

        int temp=1;
        for(int i=1;i<=Q;i++)
        {
            for(int j=temp;j<=ask[i].r;j++)
            {
                if(book[num[j]])
                {
                    add(book[num[j]],-1);

                }
                add(j,1);
                book[num[j]]=j;
            }
            temp=ask[i].r+1;
            out[ask[i].pos]=sum(ask[i].r)-sum(ask[i].l-1);

        }

        for(int i=1;i<=Q;i++)
            printf("%d\n",out[i]);


    }
    return  0;
}

 

### 离散化的概念及其在树状数组和线段树中的应用 #### 什么是离散化离散化是一种将连续的数据映射到有限集合的技术。其核心目的是减少数据范围,从而优化存储空间和计算效率。通常情况下,在处理大规模输入时,如果原始数据的取值范围过大,则会显著增加算法的时间复杂度或内存消耗。因此,通过离散化技术,可以有效地缩小数值范围并保持相对顺序不变。 --- #### 离散化的实现方法 离散化的具体实现可以通过以下方式完成: 1. **收集所有可能的关键值**:遍历整个数据集,提取出所有的关键值(例如坐标、权重等)。 2. **去重与排序**:对这些关键值进行升序排列,并去除重复项。 3. **映射回原数据**:对于每一条记录,将其对应的原始值替换为其在已排序列表中的位置索引。 以下是基于 Python 的简单离散化代码示例: ```python def discretize(values): unique_values = sorted(set(values)) # 去重并排序 mapping = {val: idx + 1 for idx, val in enumerate(unique_values)} # 映射关系 (加1是为了避免下标从0开始) return [mapping[val] for val in values] # 测试用例 original_data = [5, 8, 1, 9, 5, 1] discretized_data = discretize(original_data) print(discretized_data) # 输出:[3, 4, 1, 5, 3, 1] ``` 上述过程的核心在于构建一个字典 `mapping`,用于快速查找任意给定值的新编号[^2]。 --- #### 离散化在线段树中的应用 当面对较大的数值域时,直接创建对应大小的线段树可能导致极大的时间和空间开销。此时可通过离散化来解决这一问题。例如,在动态区间最大值查询场景中,假设存在大量稀疏分布的大数作为键值,则可先对其进行离散化再建立线段树模型。 ##### 实现细节 - 对于待操作序列 `{a_i}` 中的所有元素以及询问涉及的位置点 `(l,r)` 进行统一整理; - 使用前述提到的方法得到新的紧凑表示形式; - 构建规模适配的小型线段树执行后续任务。 这种策略特别适用于那些仅需关注局部变化趋势而不关心绝对量级的应用场合[^1]。 --- #### 离散化树状数组里的角色扮演 类似于线段树的情况,当遇到超大数据跨度的任务需求时——比如统计频率或者累积贡献等问题——同样推荐采用类似的预处理手段即离散化步骤之后再利用树状数组解决问题。因为即使理论上支持无限扩展能力,实际编程环境中还是受限于物理资源约束所以有必要采取措施加以控制。 举个例子来说就是当我们想要知道某个范围内有多少种不同的颜色出现过的时候就可以先把各种可能出现的颜色编码成较小整数然后再放进BIT里边管理起来以便高效检索答案[^3]. 下面给出一段伪代码展示如何结合两者工作: ```cpp // C++ Pseudo-code Example vector<int> BIT; // Binary Indexed Tree Array Initialization omitted here. void update(int index, int delta){ while(index < BIT.size()){ BIT[index] +=delta; index +=index &(-index); } } int query_prefix_sum(int index)const{ int res=0; while(index >0 ){ res+=BIT[index]; index -=index& (-index); } return res;} ``` 这里需要注意的是由于进行了离散变换后的索引未必是从自然数列来的故而在调用之前应当重新定位真实的地址位置. --- ### 总结 综上所述,无论是针对何种类型的高级数据结构而言,合理运用离散化技巧均能带来性能上的巨大提升效果明显优于单纯依赖蛮力枚举方案。尤其是在现代竞赛环境下更是不可或缺的一项技能掌握程度直接影响最终得分高低。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值