离散化+树状数组

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

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

DQUERY - D-query

#sorting #tree

English Vietnamese

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.csdn.net/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;
}

猜你喜欢

转载自blog.csdn.net/ljd201724114126/article/details/81154095