题解 [SDOI2009] HH的项链

发布时间 2023-07-25 11:41:07作者: 2017BeiJiang

题目链接

对于这类问区间不同数的总数,显然是不能用线段树直接维护的,毕竟不符合区间区间可加性。

考虑对于一个右端点固定的询问,哪些数字实际上是有权值的。

比如区间 1 3 3 2 3 1 2,显然,实际上对于相同的数字,只有一个是有权值的,其他权值均为 \(0\)。但是这样还是无法起到简化的作用,毕竟对于 3 3 3,要选那个 \(3\) 放权值呢?

由于我们固定右端点,所以取最右边的放权值一定可以囊括所有情况。对于上面的例子,我们应该这样放权值:

0 0 0 1 0 1 1
1 3 3 3 1 1 2

答案即为一个区间内的权值之和。

考虑如何实现上述的“固定右端点”,我们可以按照右端点从小到大排序,对其进行离线,对于新加入的右端点(颜色记作 \(col\)),若前面有与 \(col\) 相同的,将那个点的权值减一,这要求先预处理出每个点前面与其颜色相同的点的坐标,并使用树状数组进行单点修改区间查询。

点击查看代码
#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
LL read() {
    LL sum=0,flag=1; char c=getchar();
    while(c<'0'||c>'9') {if(c=='-') flag=-1; c=getchar();}
    while(c>='0'&&c<='9') {sum=sum*10+c-'0'; c=getchar();}
    return sum*flag;
}

const int N=1e6+10;
int n,m;
int a[N],p[N],lst[N];
int ans[N],tr[N];
struct node {
    int l,r,id;
}q[N];

int cmp(node x,node y) {
    return x.r<y.r;
}

int lowbit(int x) {
    return x&(-x);
}

void add(int nd,int x) {
    for(int i=nd;i<=n;i+=lowbit(i)) tr[i]+=x;
}

int query(int nd) {
    int sum=0;
    for(int i=nd;i;i-=lowbit(i)) sum+=tr[i];
    return sum;
}

int main() {
    // freopen("a.in","r",stdin);
    // freopen("a.out","w",stdout);

    n=read();
    for(int i=1;i<=n;i++) {
        a[i]=read();
        lst[i]=p[a[i]];
        p[a[i]]=i;
    }

    cin>>m;
    for(int i=1;i<=m;i++) {
        q[i].l=read(); q[i].r=read();
        q[i].id=i;
    }
    sort(q+1,q+1+m,cmp);
    int j=0;
    for(int i=1;i<=m;i++) {
        while(j+1<=q[i].r) {
            j++;
            add(j,1);
            if(lst[j]) add(lst[j],-1);
        }
        ans[q[i].id]=query(q[i].r)-query(q[i].l-1);
    }
    for(int i=1;i<=m;i++) cout<<ans[i]<<'\n';


    return 0;
}