BZOJ 2120 数颜色

题目描述 Description
墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会像你发布如下指令:

1.Q L R 询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔
2.R P Col 把第P支画笔替换为颜色Col

为了满足墨墨的要求,你知道你需要干什么了吗?

输入描述 Input Description
第1行两个整数N,M,分别代表初始画笔的数量以及墨墨会做的事情的个数。
第2行N个整数,分别代表初始画笔排中第i支画笔的颜色。
第3行到第2+M行,每行分别代表墨墨会做的一件事情,格式见题干部分。

输出描述 Output Description
对于每一个Query的询问,你需要在对应的行中给出一个数字,代表第L支画笔到第R支画笔中共有几种不同颜色的画笔。

样例输入 Sample Input
6 5
1 2 3 4 5 5
Q 1 4
Q 2 6
R 1 2
Q 1 4
Q 2 6

样例输出 Sample Output
4
4
3
4

数据范围及提示 Data Size & Hint
对于100%的数据,N≤10000,M≤10000,修改操作不多于1000次,所有的输入数据中出现的所有整数均大于等于1且不超过10^6

Luogu上好像有一样的题目?
但是,似乎,好像,也许,模拟就能过?
而BZOJ上的数据强很多

进入正题!

显然,正常情况下模拟 O(nm) O ( n m ) 是过不了的
那么,对于这种单点修改,区间查询的问题,我们可以使用:序列之王 Splay
但是,貌似裸的维护前驱的Splay会被卡?

若使用裸Splay:

维护每个点的前驱pre,维护一个以pre作为关键字从小到大排列的Splay
但是,怎么将需要查询的区间提出?
或者直接维护序列,即以排名rank作为关键字从小到大排序的Splay
但是,怎么查询区间内的值?
结果还是 O(n) O ( n ) 的查询

那咋整啊

现在最大的问题就是怎么将需要查询的区间取出?
那么,我们就可以用分块来帮忙!

分块+Splay

分块来维护区间,Splay来维护每个区间内的pre
查询:
若是整的一个块,因为块内的pre是有序的,就直接在块内查找即可(Splay操作, O(log(n)) O ( l o g ( n ) )
若是散块,则直接暴力查找(由于散块大小小于 n n ,所以时间复杂度为 O(n) O ( n )
总时间复杂度为 O(nlog(n)) O ( n l o g ( n ) )
修改:
因为题目中说了修改操作小于1000次,暴力修改即可
因为我们需要对每个块内进行排序后建树,所以就保存一下每个点排序后到了哪个位置,然后直接修改这个点,重新排序再建树即可
注意: 不仅仅需要修改这个树内的值,在它后面原本以它为前驱的点也需要修改前驱,重新排序

想明白了这些,你就离AC不远啦!

Code:

#include 
using namespace std;
#define maxn 10010
#define maxnn 1000100
int ch[maxn][2],key[maxn],f[maxn],size[maxn];

int l[maxnn];
int tag[maxn];
int col[maxn];
int pre[maxn];//pre color`s weizhi
int cnt[maxn];

struct node {
    int w;
    int pre;
    int col;
}sor[maxn];
int w[maxnn];

int sz,root[maxn],n,m,knum,s;
bool cmp(node a,node b) {return a.preint read() {
    int ans=0,flag=1;
    char c=getchar();
    while( (c>'9' || c<'0') && c!='-' ) c=getchar();
    if(c=='-') flag=-1,c=getchar();
    while(c>='0' && c<='9') ans=ans*10+c-'0',c=getchar();
    return ans*flag;
}
bool get(int x) {return ch[f[x]][1]==x;}
void update(int x) {size[x]=1+size[ch[x][0]]+size[ch[x][1]];}
void rotate(int x) {
    int fa=f[x],ffa=f[fa],w=get(x);
    ch[fa][w]=ch[x][w^1],f[ch[fa][w]]=fa;
    ch[x][w^1]=fa,f[fa]=x;
    f[x]=ffa;
    if(ffa)
        ch[ffa][ch[ffa][1]==fa]=x;
    update(fa),update(x);
    return ;
}
void splay(int rt,int x,int tar) {
    for(int fa;(fa=f[x])!=tar;rotate(x))
        if(f[fa]!=tar)
            rotate(get(x)==get(fa)?fa:x);
    if(!tar)
        root[rt]=x;
    return ;
}
int find(int rt,int x) {//查找前驱小于l的点数量
    int now=root[rt],ans=0;
    while(1) {
        if(now==0) return ans;
        if(x0];
        else {
            ans+=(ch[now][0]?size[ch[now][0]]:0);
            if(x==key[now]) {
                splay(rt,now,0);
                return ans;
            }
            ans+=1;
            now=ch[now][1];
        }
    } 
}
int findx(int rt,int x) {
    int now=root[rt];
    while(1) {
        if(ch[now][0] && x<=size[ch[now][0]])
            now=ch[now][0];
        else {
            int tmp=(ch[now][0]?size[ch[now][0]]:0)+1;
            if(x<=tmp) return now;
            x-=tmp;
            now=ch[now][1];
        } 
    }
}
int build(int l,int r,int fa) {//建树
    if(l>r) return 0;
    int mid=(l+r)>>1;
    int now=++sz;
    f[now]=fa;
    size[now]=1;
    key[now]=sor[mid].pre;

    ch[now][0]=build(l,mid-1,now);
    ch[now][1]=build(mid+1,r,now);
    update(now);
    return now;
}
void Q() {
    int ans=0;
    int l=read(),r=read();
    for(int i=l;i<=min(r,s*tag[l]);i++)
        if(pre[i]if(tag[l]!=tag[r])
        for(int i=s*(tag[r]-1)+1;i<=r;i++)
            if(pre[i]for(int i=tag[l]+1;i<=tag[r]-1;i++)
        ans+=find(i,l);
    printf("%d\n",ans);
    return ;
}
void R() {
    int x=read(),color=read();
    if(color==col[x]) return;
    int before=pre[x];
    pre[x]=0;
    sor[w[x]].pre=0;
    col[x]=color;
    sor[w[x]].col=color;
    cnt[tag[x]]=1;//给块打上修改过的标记,方便后面排序
    for(int i=n;i>=1;i--) {
        if(pre[i]==x) {//若该点前驱为x,则修改前驱为x原来的前驱
            pre[i]=before;
            sor[w[i]].pre=before;
            cnt[tag[i]]=1;
        }
        if(i>x && pre[i]//若该点位置在x后,前驱在x前,且颜色与修改后的x相同,则将x的前驱修改为该点的前驱,该点的前驱修改为x
            pre[x]=pre[i];
            sor[w[x]].pre=pre[i];
            pre[i]=x;
            sor[w[i]].pre=x;
            cnt[tag[i]]=1;
        }
        if(i0)//若后面没有能“帮助”x找到前驱的点,就直接找吧
            pre[x]=i,sor[w[x]].pre=i;
    }
    for(int i=1;i<=knum;i++) {
        if(!cnt[i]) continue;
        sort(sor+(i-1)*s+1,sor+min(i*s,n)+1,cmp);//重新排序
        sz=(i-1)*s;//覆盖原来Splay上的节点
        root[i]=build((i-1)*s+1,min(i*s,n),0);
        for(int j=(i-1)*s+1;j<=min(i*s,n);++j)//修改位置对应的情况
            w[sor[j].w]=j;
        cnt[i]=0;
    }
    return ;
}
int main() {
    n=read(),m=read();
    s=sqrt(n);
    knum=n/s;
    if(n%s) ++knum;

    for(int i=1;i<=n;i++) {
        tag[i]=(i-1)/s+1;
        col[i]=read();
        sor[i].col=col[i];
        pre[i]=l[col[i]];
        sor[i].pre=pre[i];
        l[col[i]]=i;//每种不同的颜色当前最后一个的位置
        sor[i].w=i;
    }
    for(int i=1;i<=knum;i++) {
        sort(sor+(i-1)*s+1,sor+min(i*s,n)+1,cmp);
        root[i]=build((i-1)*s+1,min(i*s,n),0);
    }
    for(int i=1;i<=n;i++)
        w[sor[i].w]=i;//保存每个点在排序后到了哪个node里面
    while(m--) {
        char str[10];
        scanf("%s",str);
        if(str[0]=='Q')
            Q();
        else
            R();
    }
    return 0;
}

你可能感兴趣的:(【数据结构】Splay,【数据结构】分块)