2018牛客多校第一场

J-Different Integers

题意:有n个数,q次查询,每次查询给出 L , R 两个数,求1~L 和R~n两个区间内共有多少不同的数。

此题和SPOJ 3267相似,只是SPOJ 3267是直接求区间(L~R)内不同的数的个数,所以我们可以换个角度看看这道题的要求,如果将给出的数字序列扩大一倍,也就是在将整个序列复制到原序列后面,这样我们查询的原序列中的1~L和R~N就变成了新的长度为2*n的序列中的R~n+L,不得不说真的这个想法太好了。当明白了这一点以后,我们就可以愉快的用SPOJ 3267一样的方法来做了。

首先是树状数组的做法:

对于每一个要查询的区间,区间内出现的数的种类数就是每个数字只算一次,如果之前出现了再出现的时候也只会算一次,所以是不是可以考虑到,当一个数在区间内第一次出现的时候,它对这个区间的答案是有贡献的,数的种类要+1,但当这个数第二次出现时,我们就让它对区间做贡献而把区间内它上一次出现的位置的贡献置为0,为什么要从前往后这样更新贡献呢?我的理解是这样子方便记录,用一个数组记录每个数字上一次出现的位置,那么从前往后扫过去的就可以自然的记录了。而要求的最终答案就是区间L到R的每个位置贡献的和。由于要不断单点更新和区间求和,所以在树状数组上面操作。具体步骤简单说一下:先将询问按照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++)
        {
            for(;cur<=Q[i].r&&cur<=n*2;cur++)
            {
                if(last[a[cur]])//当前这个位置的数以前出现过
                    Add(last[a[cur]],-1);//以前的位置的贡献去掉
                last[a[cur]]=cur;//更新数字上一个出现的位置
                Add(last[a[cur]],1);//当前位置的贡献更新
            }
            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 棵线段树,每个线段树是以每个位置的数为根,比如说该建立第i 个线段树了,如果这个数字之前没有出现过,那么我们直接以位置为划分依据,在线段树上包含这个位置的加1即可,表示这个区间上又多了一种数。但是如果这个数字出现过了,我们先求出上一个同样数在哪里出现,我们就在第i 个线段树上以那个位置为划分依据给它减去1,在在第i 个线段树上 包含位置的i 的区间加1,这样我们就保证了 区间中数字不重复,只保留最后一个。

但是这道题直接复制序列当做2*n的长度来做会超时。所以我们可以先求出只出现在 L-R中有多少种 x (求出每种数的最左出现和最右出现 当一个数 li>=L&&ri<=R说明它只出现在 L-R中),用种数num 减去 x ,ans=num-x。 

附上代码:

#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;
}

莫队做法:

#include<bits/stdc++.h>

using namespace std;

const int maxn = 1e5 + 5;

int block_len;

struct node {

    int l, r, id, block;

    node() {}

    node(int l, int r, int id): l(l), r(r), id(id)

    {

        block = l / block_len;

    }

    bool operator<(const node&x)const

    {

        if(block != x.block) {

            return l < x.l;

        }

        if(block & 1) {

            return r < x.r;

        } else {

            return r > x.r;

        }

    }

} query[maxn];

int n, q, sum, tot;

int cnt[maxn], a[maxn], ans[maxn];

int main()

{

    while(scanf("%d%d", &n, &q) != EOF) {

        tot=0;

        memset(cnt,0,sizeof cnt);

        sum=0;

        block_len = sqrt(n);

        for(int i = 1; i <= n; i++) {

            scanf("%d", &a[i]);

            cnt[a[i]]++;

            if(cnt[a[i]] == 1) {

                sum++;

            }

        }

        for(int i = 1; i <= q; i++) {

            int l, r;

            scanf("%d%d", &l, &r);

            if(r <= l + 1) {

                ans[i] = sum;

            } else {

                query[++tot] = node(l + 1, r - 1, i);

            }

        }

        sort(query + 1, query + tot + 1);

        int l = 1, r = 0;

        for(int i = 1; i <= tot; i++) {

            node& qr = query[i];

            while(qr.r > r) {

                r++;

                cnt[a[r]]--;

                if(cnt[a[r]] == 0) {

                    sum--;

                }

            }

            while(qr.l < l) {

                l--;

                cnt[a[l]]--;

                if(cnt[a[l]] == 0) {

                    sum--;

                }

            }

            while(qr.r < r) {

                cnt[a[r]]++;

                if(cnt[a[r]] == 1) {

                    sum++;

                }

                r--;

            }

            while(qr.l > l) {

                cnt[a[l]]++;

                if(cnt[a[l]] == 1) {

                    sum++;

                }

                l++;

            }

            ans[qr.id] = sum;

        }

        for(int i = 1; i <= q; i++) {

            printf("%d\n", ans[i]);

        }

    }

    return 0;

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值