洛谷-P4113

题目描述
萧薰儿是古国的公主,平时的一大爱好是采花。

今天天气晴朗,阳光明媚,公主清晨便去了皇宫中新建的花园采花。

花园足够大,容纳了n朵花,花有c种颜色(用整数1-c表示),且花是排成一排的,以便于公主采花。公主每次采花后会统计采到的花的颜色数,颜色数越多她会越高兴!同时,她有一癖好,她不允许最后自己采到的花中,某一颜色的花只有一朵。为此,公主每采一朵花,要么此前已采到此颜色的花,要么有相当正确的直觉告诉她,她必能再次采到此颜色的花。

由于时间关系,公主只能走过花园连续的一段进行采花,便让女仆福涵洁安排行程。福涵洁综合各种因素拟定了m个行程,然后一一向你询问公主能采到多少朵花(她知道你是编程高手,定能快速给出答案!),最后会选择令公主最高兴的行程(为了拿到更多奖金!)。

输入格式
第一行四个空格隔开的整数n、c以及m。接下来一行n个空格隔开的整数,每个数在[1, c]间,第i个数表示第i朵花的颜色。接下来m行每行两个空格隔开的整数l和r(l ≤ r),表示女仆安排的行程为公主经过第l到第r朵花进行采花。

输出格式
共m行,每行一个整数,第i个数表示公主在女仆的第i个行程中能采到的花的颜色数。

输入输出样例
输入 #1复制

5 3 5
1 2 2 3 1
1 5
1 2
2 2
2 3
3 5

输出 #1复制

2
0
0
1
0

说明/提示
对于100%的数据, 1 n 2 1 0 6 1 ≤ n ≤ 2*10^6 ,c ≤ n, m 2 1 0 6 m ≤2*10^6
本题有两个subtask
subtask1保证n,m,c≤ 3 1 0 5 3∗10^5 ,占100分.

subtask2保证n,m,c≤ 2 1 0 6 2∗10^6 ,占100分.

解题思路,对于第一个subtask的处理,就只需要使用莫队算法处理一下,计算区间中出现个数大于等于2的数量就可以啦。
代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=3e6+7;
void read(int &x)
{
    char c;
    int f=1;
    x=0;
    c=getchar();
    while(c<'0' || c>'9'){
        if(c=='-') f=-1;
        c=getchar();
    }
    while(c>='0' && c<='9'){
        x=x*10+c-'0';
        c=getchar();
    }
    x*=f;
    //return x*f;
}
struct que
{
    int l,r,id,pos;
    bool operator <(que a)const {
        return pos==a.pos?(pos&1?r<a.r:r>a.r):pos<a.pos;
    }
}q[maxn];
bitset<maxn> t;
int num[maxn],cnt,a[maxn];
void add(int x)
{
    num[x]++;
    if(num[x]==2)cnt++;
}
void del(int x)
{
    num[x]--;
    if(num[x]==1)cnt--;
}
int ans[maxn];
int main()
{
    int n,m,len,c;
    read(n),read(c),read(m);len=sqrt(n);
    for(int i=1;i<=n;i++){
        read(a[i]);
    }
    for(int i=1;i<=m;i++){
        int l,r;
        read(l),read(r);
        q[i]=que{l,r,i,(l-1)/len+1};
    }
    sort(q+1,1+q+m);
    int l=1,r=0;
    //cout<<endl;
    for(int i=1;i<=m;i++){
        while(l<q[i].l){
            del(a[l++]);
        }
        while(l>q[i].l){
            add(a[--l]);
        }
        while(r<q[i].r){
            add(a[++r]);
        }
        while(r>q[i].r){
            del(a[r--]);
        }
        ans[q[i].id]=cnt;
    }
    for(int i=1;i<=m;i++){
        printf("%d\n",ans[i]);
    }
    return 0;
}

对于subtask2的解法,其实也不能这么说,因为只是一种时间复杂度更好的解题思路,上面是用莫队算法过了subtask1的数据,然后对于subtask2的数据莫队算法过好像有点困难,因为它的时间复杂度为O(n*sqrt(n)),sqrt(n)是分块的长度,其他的证明我太菜了,不怎么会,然后我们来康康另一种思路,就是寻找出现了两次以上的数的个数,然后我们想一下,我们曾经是不是做过计算区间中出现过的数的种类数。这种题最开始我们除了使用过莫队算法,还使用过树状数组来维护区间中的每个值的贡献值,因为如果有重复的数,我们也只需要计算一次,那么我们就只认为这个数最后一次出现的地方对答案有贡献值,然后我们就可以用树状数组前缀和计算这个贡献值啦,但是这里我们需要计算出现两次或以上的数的个数怎么办呢?我们就保证能够出现两次,从遍历的右区间开始,我们只统计已经出现了两次的数的贡献值,并且这个贡献值是由倒数第二次出现的位置提供的。然后我们就转换为那个题啦。
代码:

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e7+7;
void read(int &x)
{
    x=0;
    int f=1;
    char c=getchar();
    while(c<'0' || c>'9'){
        if(c=='-')f=-1;
        c=getchar();
    }
    while(c>='0' && c<='9'){
        x=x*10+c-'0';
        c=getchar();
    }
    x*=f;
}
struct node
{
    int l,r,id;
    bool operator <(node a)const{
        return r<a.r;
    }
}q[maxn];
int sum[maxn],n,m,c,a[maxn],pos[maxn],pre[maxn],ans[maxn];
void add(int x,int d)
{
    if(x==0)return ;
    while(x<=n){
        sum[x]+=d;
        x+=(x&-x);
    }
}
int ask(int x)
{
    int res=0;
    while(x){
        res+=sum[x];
        x-=(x&-x);
    }
    return res;
}
int main()
{
    read(n),read(c),read(m);
    //cout<<n<<' '<<c<<' '<<m<<endl;
    for(int i=1;i<=n;i++){
        read(a[i]);
        pre[i]=pos[a[i]];
        pos[a[i]]=i;
    }
    for(int i=1;i<=m;i++){
        int l,r;
        read(l),read(r);
        q[i]=node{l,r,i};
    }
//    for(int i=1;i<=n;i++){
//        printf("%d ",pre[i]);
//    }
//    printf("\n");
    sort(q+1,q+1+m);
    int l=1;
    for(int i=1;i<=n;i++){
        add(pre[i],1);
        add(pre[pre[i]],-1);
        //cout<<q[i].l<<' '<<q[i].r<<' '<<q[i].id<<endl;
        for(;l<=m && i==q[l].r;l++)
        ans[q[l].id]=ask(q[l].r)-ask(q[l].l-1);
    }
    for(int i=1;i<=m;i++){
        printf("%d\n",ans[i]);
    }
    return 0;
}
发布了34 篇原创文章 · 获赞 3 · 访问量 251

猜你喜欢

转载自blog.csdn.net/qq_44641782/article/details/103193141