Lydsy2120 数颜色(分块写法)

Posted wzx-rs-sthn

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lydsy2120 数颜色(分块写法)相关的知识,希望对你有一定的参考价值。

Description

墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。
墨墨会像你发布如下指令: 
1、 Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔。 
2、 R P Col 把第P支画笔替换为颜色Col。
为了满足墨墨的要求,你知道你需要干什么了吗?

Input

第1行两个整数N,M,分别代表初始画笔的数量以及墨墨会做的事情的个数。
第2行N个整数,分别代表初始画笔排中第i支画笔的颜色。
第3行到第2+M行,每行分别代表墨墨会做的一件事情,格式见题干部分。
N≤10000,M≤10000,修改操作不多于1000次,所有的输入数据中出现的所有整数均大于等于1且不超过10^6

Output

对于每一个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



思路:
对于这一题,可以用分块,也可以用带修改的莫队;
我先讲下分块写法
可以用一个pre数字存每一个颜色上一次出现的位置;
如果这个pre位置是小于询问区间的左端点的,那么代表这个颜色是在这个询问区间内第一次出现的;
这样就可以查找这个区间内有多少个不同的颜色;
然后,我们可以定义一个last数组,存这个颜色的最后出现位置;
同时last数组可以方便我们更好的记录pre;
然后是修改操作;
可以重新再把每一个颜色的pre重新构建;

inline void findout(ll x,ll y)
{
    for(ll i=1;i<=n;i++)
        last[color[i]]=0;
    color[x]=y;
    for(ll i=1;i<=n;i++)
    {
        ll num=pre[i];
        pre[i]=last[color[i]];
        last[color[i]]=i;
        if(pre[i]!=num)
            dowork(i);
    }
}
也可以找到与被修改的颜色相关的点,然后用if修改关于这个颜色的pre,last数组;
这样修改将比上面的代码更快;
代码,最后会给出;

最后,总结下思路,
分块,排序后二分找整块,暴力枚举小棱块,查找pre
位置小于询问区间的左端的那个颜色

附上熬夜修改的代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll read()
{
    ll a=0,f=1; char c=getchar();
    while (c<0||c>9) {if (c==-) f=-1; c=getchar();}
    while (c>=0&&c<=9) {a=a*10+c-0; c=getchar();}
    return a*f;
}
ll n,m,blo;
ll a[1000001],color[1000001],sv[1000001];//a数组是每个位置所处的分块,
                                         //sv是按每个分块排序后的pre值 
ll pre[1000001],last[1000001];
inline ll L(ll x)//懒人专用
                 //求每个分块左右的端点 
{
    return (x-1)*blo+1;
}
inline ll R(ll x)
{
    return x*blo;
}
inline void dowork(ll x)
{
    for(ll i=L(a[x]);i<=R(a[x]);i++)
        sv[i]=pre[i];
    sort(sv+L(a[x]),sv+R(a[x])+1);//pre修改后,重新排序构建,便于二分查找 
}
inline void findout(ll x,ll y)//x表示被修改的位置,y是要变成的颜色 
{
    ll cox=color[x];//记录这个颜色 
    color[x]=y;    //修改 
    ll now=last[cox];//记录这个要被修改的颜色的最后一个点 
    if(now==x)//如果最后一个点要被修改 
        last[cox]=pre[x];//那么这个颜色的最后一个点,将是被修改的颜色的pre 
    else if(now>x)//若  x.........now  不会出现x<now的情况,因为now=last[cox]; 
    {
        while(pre[now]!=x)//不断往前找,使得pre[now]==x 
            now=pre[now];
        pre[now]=pre[x];// pre[x]......x......now  因为x会被修改,所以 pre[now]=pre[x]
        dowork(now);//因为pre被修改了,所以now所在的块重新排序 
    }
    now=last[y];//记下y颜色的最后一个位置 
    if(now<x)//因为 y!=x 所以 不考虑now==x的情况 
    {
        last[y]=x;// now......x  y颜色的最后位置将会变成x 
        pre[x]=now;
        dowork(x);//重新排序 
    }
    else if(now>x)//x......now
    {
        while(pre[now]>x)//不断找到,使得pre[now]<x 
            now=pre[now];
        pre[x]=pre[now];//pre[now]....x.....now
        pre[now]=x;
        dowork(x);
        dowork(now);
    }
}
//inline void findout(ll x,ll y) //重新构建方法,比上面的更慢 
//{
//    for(ll i=1;i<=n;i++)
//        last[color[i]]=0;  //所以颜色的最后一个位置清零 
//    color[x]=y;
//    for(ll i=1;i<=n;i++)//重新枚举构建 
//    {
//        ll num=pre[i];
//        pre[i]=last[color[i]];
//        last[color[i]]=i;
//        if(pre[i]!=num)//如果重新构建之前 与构建之后的pre值改变了 
//            dowork(i);//就整块分块内 重新排序 
//    }
//}
inline ll reallyans(ll x,ll y)
{
    ll sum=0;
    if(a[x]==a[y])//如果询问区间处在一个分块,就暴力 
    {
        for(ll i=x;i<=y;i++)
        if(pre[i]<x) //查找pre位置小于询问区间的左端的那个颜色
            sum++;
        return sum;
    }
    else
    {
        for(ll i=a[x]+1;i<=a[y]-1;i++)
        {
            ll l=L(i),r=R(i),ss=0,mid;
            while(l<=r)
            {
                mid=(l+r)>>1;
                if(sv[mid]<x)
                    l=mid+1,
                    ss=mid-L(i)+1;//如果中间的那个sv的位置都小于x
                                 //因为是排好序的,所以左边的位置都小于x 
                else
                    r=mid-1;
            }
            sum+=ss;
        }//二分找位置小于询问区间的左端的那个颜色
        for(ll i=x;i<=R(a[x]);i++)//暴模边角 
        if(pre[i]<x)
            sum++;
        for(ll i=L(a[y]);i<=y;i++)
        if(pre[i]<x)
            sum++;
        return sum;
    }
}
int main()
{
    n=read();m=read();
    blo=sqrt(n);
    for(ll i=1;i<=n;i++)
    {
        color[i]=read();
        a[i]=(i-1)/blo+1;
        pre[i]=last[color[i]];
        last[color[i]]=i;
    }//记录pre和last的值 
    for(ll i=1;i<=n;i++)
        sv[i]=pre[i];
    for(ll i=1;i<=a[n];i++)
        sort(sv+L(i),sv+R(i)+1);//每个分块内都排好序 
    char cc[4];
    ll ans;
    for(ll i=1;i<=m;i++)
    {
        scanf("%s",cc);
        ll x=read(),y=read();
        if(cc[0]==R)
            findout(x,y);
        else if(cc[0]==Q)
        {
            ans=reallyans(x,y);
            printf("%lld
",ans);
        }
    }
    return 0;//别忘了return 0 ^_^ 
}

 

 

以上是关于Lydsy2120 数颜色(分块写法)的主要内容,如果未能解决你的问题,请参考以下文章

bzoj2453/2120 数颜色

BZOJ 2120 数颜色 (分块,蛮力)

BZOJ2120 数颜色 分块+二分法

bzoj2120: 数颜色 [莫队][分块]

bzoj2120: 数颜色(BIT套主席树+set/分块)

Bzoj 2453: 维护队列 && Bzoj 2120: 数颜色 分块,bitset