牛客网暑假训练第一场——J Different Integers(莫队算法 & 树状数组)

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

时间限制:C/C++ 2秒,其他语言4秒
空间限制:C/C++ 524288K,其他语言1048576K
64bit IO Format: %lld
题目描述
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.

题意:给一个序列,再给出个数为1e5的询问区间,问如果去掉区间内的数,剩余的数有多少种不同的数。注意给的区间是L~R,删去的是L+1….R-1的数,保留端点L和R。
该题目的正解是用树状数组。因此用莫队写会有些玄学,甚至有时得看评测机心情给不给过,同样的代码也许交一次超时交一次能过。
莫队写法即先对序列分块,然后对询问区间按照分块排序,若两询问区间L边界在同一块内,按R排序,若不在同一区间,按分块顺序排序。
将查询离线化后即可从头开始遍历,根据排序顺序移动指针查询结果。每次移动一步,来判断新加入和减少数据对结果的影响。注意不是直接不分块对区间进行L相同比较R的排序,而是分块后根据所在块号进行排序,块内排R,块外排L,并且不要和分块算法弄混,分块算法没有排序操作,并且他分块时进行块内数据的预处理,而莫队并没有预处理,莫队的分块是为了排序,排序时为了离线化,并且莫队就是一格一格移动来判断数据对结果的影响的,复杂度是一个大于O(N)的遍历。只不过利用排序可以一次性遍历最大效率的得到多个查询的结果。

代码如下:

#include<bits/stdc++.h>///莫队
using namespace std;
const int maxn=100005;
struct node
{
    int l,r,id;
} que[maxn];
int n,q;
int a[maxn];
int pos[maxn];
int num[maxn];
int L,R;
int ans[maxn];
int cnt=0;
bool cmp(node a,node b)
{
    if(pos[a.l]==pos[b.l])return a.r<b.r;
    return pos[a.l]<pos[b.l];
}
void del(int x)
{
    if(num[a[x]]==0)cnt++;
    num[a[x]]++;
}
void add(int x)
{
    num[a[x]]--;
    if(num[a[x]]==0)cnt--;
}
int main()
{
    while(scanf("%d%d",&n,&q)!=EOF)
    {
        L=1,R=0;
        memset(num,0,sizeof num);
        int sz=1000;
        cnt=0;
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&a[i]);
            pos[i]=i/sz;
            if(num[a[i]]==0)cnt++;
            num[a[i]]++;
        }
        for(int i=1; i<=q; i++)
        {
            scanf("%d%d",&que[i].l,&que[i].r);
            que[i].l++,que[i].r--;
            que[i].id=i;
        }
        sort(que+1,que+1+q,cmp);
        for(int i=1; i<=q; i++)
        {
            while(L<que[i].l) del(L),L++;
            while(L>que[i].l) L--,add(L);
            while(R<que[i].r) add(++R);
            while(R>que[i].r) del(R--);
            ans[que[i].id]=cnt;
        }
        for(int i=1;i<=q;i++)printf("%d
",ans[i]);
    }
}

正解的树状数组解法如下:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int maxn=100005;
const int inf=1000000007;
struct node
{
    int l,r,id;
}que[maxn];
bool cmp(node a,node b)
{
    return a.r<b.r;
}
int n,q;
int num[maxn];
int first[maxn],last[maxn];
int tree[maxn],ans[maxn];
int lowbit(int x)
{
    return (-x)&x;
}
int updata(int x,int tmp)
{
    for(int i=que[x].l;i<=n;i+=lowbit(i))
        tmp-=tree[i];
    return tmp;
}
void query(int x)
{
    for(int i=x;i>0;i-=lowbit(i))
        tree[i]++;
}
int main()
{
    int cnt=0;
    while(scanf("%d%d",&n,&q)!=EOF)
    {
        cnt=0;
        memset(first,-1,sizeof first);
        memset(tree,0,sizeof tree);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&num[i]);
            last[num[i]]=i;
            if(first[num[i]]==-1)first[num[i]]=i,cnt++;
        }
        for(int i=0;i<q;i++)
        {
            scanf("%d%d",&que[i].l,&que[i].r);
            que[i].id=i;
        }
        sort(que,que+q,cmp);///询问排序,按R右边界从小到大
        int j=0;
        for(int i=1;i<=n;i++)
        {
            while(j<q&&que[j].r==i) ans[que[j].id]=updata(j,cnt),j++;
            if(last[num[i]]==i) query(first[num[i]]-1);
        }
        for(int i=0;i<q;i++)printf("%d
",ans[i]);
    }
}
原文地址:https://www.cnblogs.com/kuronekonano/p/11135740.html