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

题目:

链接: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.

题意 :给 a1,a2,a3,,,an 询问q次 (i,j) 每次问 a1, a2, ..., ai, aj, aj + 1, ..., an.有多少种数。

解法一:离线的树状数组。

可以将数组复制一份在后面求R-L+n.

/**
先将询问按照r排序
然后依次将数放进数组
如果这个数出现过 将之前位置清零,当前位置+1
如果这个是没有出现过
就在当前位置+1

求 L-R 中有多少种数,
这里把 1-L 中把一种数最后出现的位置作为贡献值,这样 Sum(R) - Sum(L-1) 就有正确性。
注意当我们更新到R时, Sum(L)不是 1-L中的数的个数(只能表示在1-R中只出现在1-L中的数有多少种),
Sum(R)才是表示 1-R中有多少种数。
*/
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long int ll;
const int maxn = 100000;
struct Query{
    int l,r,id;
}Q[maxn+5];
int n,q,a[maxn*2+5],C[maxn*2+5],ans[maxn+5],last[maxn+5];
int lowbit(int x)
{
    return x&-x;
}
int Sum(int x)
{
    int sum = 0;
    while(x>0) sum+=C[x],x-=lowbit(x);
    return sum;
}
void Add(int x,int d)
{
    while(x<=n*2) C[x]+=d,x+=lowbit(x);
}
bool cmp(Query n1,Query n2)
{
    return n1.r < n2.r;
}
int main()
{
    while(~scanf("%d %d",&n,&q))
    {
        for(int i=1;i<=n;i++) scanf("%d",&a[i]), a[n+i] = a[i];

        for(int i=1;i<=q;i++)
        {
            int l,r;
            scanf("%d %d",&l,&r);
            Q[i].l = r, Q[i].r = n+l, Q[i].id = i;
        }
        sort(Q+1,Q+1+q,cmp);
        for(int i=1;i<=n*2;i++) C[i] = 0;
        for(int i=1;i<=n;i++) last[i] = 0;
        int cur = 1;
        for(int i=1;i<=q;i++)
        {
            while(cur<=Q[i].r&&cur<=n*2)
            {
                if(last[a[cur]]) Add(last[a[cur]],-1);//把以前的贡献去掉
                last[a[cur]] = cur, Add(last[a[cur]],1);//最新的贡献
                cur++;
            }
            ans[Q[i].id] = Sum(Q[i].r) - Sum(Q[i].l-1);
        }
        for(int i=1;i<=q;i++) printf("%d\n",ans[i]);
    }
    return 0;
}

解法二:主席树。

主席树求L-R中有多少个数和树状数组一样的思想只是主席树可以在线因为主席树相当存了n个树状数组嘛。

不过当 复制数组用主席树会超时。

我们可以先求出只出现在 L-R中有多少种 x (求出每种数的最左出现和最右出现 当一个数 li>=L&&ri<=R说明它只出现在 L-R中),用种数num 减去 x ,ans=num-x。 

给出 <SPOJ DQUERY - D-query> 的大佬题解:https://blog.csdn.net/VictorZC8/article/details/52780846

/**
这里的主席树和 《SPOJ DQUERY - D-query》中 主席树(1-n代表得不一样)有区别,
这里的主席树就是经典的求kth。我也是做了这题才知道
主席树是可以如此灵活运用的,菜啊!
**/
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn = 100000;
typedef long long int ll;
int n,a[maxn+5],b[maxn+5],th[maxn+5],l[maxn+5],r[maxn+5];
int tot,sz,root[maxn*2+5],ls[maxn*50+5],rs[maxn*50+5],sum[maxn*50+5];
void build(int &v,int l,int r)
{
    v=++tot;
    sum[v] = 0;
    if(l==r) return;
    int mid = (l+r)>>1;
    build(ls[v],l,mid);
    build(rs[v],mid+1,r);
}
void updata(int &v,int last,int l,int r,int q)
{
    v = ++tot;
    ls[v] = ls[last];
    rs[v] = rs[last];
    sum[v] = sum[last]+1;
    if(l==r) return;
    int mid = (l+r)>>1;
    if(q<=mid) updata(ls[v],ls[last],l,mid,q);
    if(q>mid) updata(rs[v],rs[last],mid+1,r,q);
}
int query(int v,int last,int l,int r,int L,int R)
{
    if(r<L||l>R) return 0;
    int mid = (l+r)>>1;
    if(L<=l&&r<=R) return sum[v] - sum[last];
    int cost = 0;
    if(mid>=L) cost+=query(ls[v],ls[last],l,mid,L,R);
    if(mid<R) cost+=query(rs[v],rs[last],mid+1,r,L,R);
    return cost;
}
int q,pos[maxn+5],num;
struct Node
{
    int l,r;
} P[maxn+5];
bool cmp(Node n1, Node n2)
{
    return n1.l < n2.l;
}
int main()
{
    while(~scanf("%d %d",&n,&q))
    {
        num = tot = 0;
        for(int i=0; i<=n; i++) pos[i] = 0;
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&a[i]);
        }
        for(int i=1; i<=n; i++) r[i]=l[i]=0;
        for(int i=1; i<=n; i++)
        {
            r[a[i]] = i;
            if(l[a[i]]==0) l[a[i]] = i;
        }
        for(int i=1; i<=n; i++)
        {
            if(l[i])
            {
                num++;
                P[num].l = l[i], P[num].r = r[i];
            }
        }
        sort(P+1,P+num+1,cmp);
        build(root[0],1,n);
        for(int i=1; i<=num; i++) updata(root[i],root[i-1],1,n,P[i].r);
        P[num+1].l = n+1;//加一个边界,以防二分有可能找不到值 昨天就是没有写这里挂了。
        root[num+1] = root[num];
        while(q--)
        {
            int L,R;
            scanf("%d %d",&L,&R);
            if(R<=L||L+1==R) printf("%d\n",num);
            else
            {
                L++,R--;
                int li=0,ri=num+1,mid;
                while(ri-li>1)
                {
                    mid = (li+ri)/2;
                    if(P[mid].l>=L) ri = mid;
                    else li = mid;
                }
                printf("%d\n",num-query(root[num+1],root[ri-1],1,n,L,R));
            }
        }
    }
    return 0;
}

/*
6 3
1 2 1 2 3 4
2 4
1 6
3 5

5 3
1 1 1 1 1
1 1
1 5
2 4
*/

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值