BZOJ2120 数颜色(分块)

【题解】

用数组pre[i]记录颜色i上一次出现的位置 
则颜色C在{left,right}中第一次出现等价于:pre[C]<left 

分块后,在每块中将pre值升序排列 
先考虑查询:两头不完整的块内暴力,完整块之间二分查找小于left的pre值 
在考虑修改:将第x个数颜色变为y,会影响到:
1. 原来pre[]==x的数的新pre值:在x之后的每个块中二分查找pre[]==x的值是否存在,若存在,就在这个块中暴力找到该位置 
2. x及 x位置后第一个颜色为y 的新pre值:将color数组复制一份,并对其也在各个块内部排序,然后仿照上一种情况二分查找该颜色在块内是否存在 
  注意:若x后不存在颜色为y的位置,pre[x]应修改为last[y]表示颜色y最后一次出现的位置 
之后将color,pre,last三个数组一起维护 


【代码】

#include<stdio.h>
#include<stdlib.h>
#define SIZE 100
int block[10005],L[105],R[105],color[10005],num[10005],last[1000005],pre[10005],b[10005];
int n,cnt;
void jh(int* a,int* b)
{
    int t=*a;
    *a=*b;
    *b=t;
}
void kp(int a[],int low,int high)
{
    int i=low,j=high,mid=a[(i+j)/2];
    while(i<j)
    {
        while(a[i]<mid) i++;
        while(a[j]>mid) j--;
        if(i<=j)
        {
            jh(&a[i],&a[j]);
            i++;
            j--;
        }
    }
    if(j>low) kp(a,low,j);
    if(i<high) kp(a,i,high);
}
int finddown(int a[],int N,int left,int right)//返回:a[left~right]中最后一个比N小的数的位置 
{
    int mid;
    if(a[left]>=N) return left-1;
    while(left<right)
    {
        mid=(left+right+1)/2;
        if(a[mid]<N) left=mid;
        else right=mid-1;
    }
    return left;
}
int find(int x)//返回[x,n]中第一个pre[i]==x的数i的位置 
{
    int i,j,t;
    for(i=x;i<=R[block[x]];i++)
        if(pre[i]==x) return i;
    for(i=block[x]+1;i<=cnt;i++)
    {
        t=finddown(b,x,L[i],R[i]);
        if(t<R[i]&&b[t+1]==x)
            for(j=L[i];j<=R[i];j++)
                if(pre[j]==x) return j;
    }
    return n+1;
}
int findnum(int x,int C)//返回x之后第一个color[i]==C的i的位置 
{
    int i,j,t;
    if(x==n) return n+1;
    for(i=x+1;i<=R[block[x]];i++)
        if(color[i]==C) return i;
    for(i=block[x]+1;i<=cnt;i++)
    {
        t=finddown(num,C,L[i],R[i]);
        if(t<R[i]&&num[t+1]==C)
            for(j=L[i];j<=R[i];j++)
                if(color[j]==C) return j;
    }
    return n+1;
}
int query(int x,int y)
{
    int ans=0,i;
    if(block[x]+1>=block[y])
    {
        for(i=x;i<=y;i++)
            if(pre[i]<x) ans++;
        return ans;
    }
    for(i=x;i<=R[block[x]];i++)
        if(pre[i]<x) ans++;
    for(i=L[block[y]];i<=y;i++)
        if(pre[i]<x) ans++;
    for(i=block[x]+1;i<block[y];i++)
        ans+=finddown(b,x,L[i],R[i])-L[i]+1;
    return ans;
}
int main()
{
    char opt;
    int m,i,x,y,t;
    scanf("%d%d",&n,&m);
    cnt=(n-1)/SIZE+1;
    for(i=1;i<=n;i++)
    {
        scanf("%d",&color[i]);
        num[i]=color[i];
        b[i]=pre[i]=last[color[i]];
        last[color[i]]=i;
        block[i]=(i-1)/SIZE+1;
    }
    for(i=1;i<=cnt;i++)
    {
        L[i]=(i-1)*SIZE+1;
        R[i]=i*SIZE;
    }
    R[cnt]=n;
    for(i=1;i<=cnt;i++)
    {
        kp(b,L[i],R[i]);
        kp(num,L[i],R[i]);
    }
    for(;m>0;m--)
    {
        scanf("\n%c %d %d",&opt,&x,&y);
        if(opt=='R')
        {
            if(color[x]==y) continue;//特判:替换前后颜色不变 
            t=find(x);
            if(t<=n)
            {
                pre[t]=pre[x];
                for(i=L[block[t]];i<=R[block[t]];i++)
                    b[i]=pre[i];
                kp(b,L[block[t]],R[block[t]]);
            }
            else last[color[x]]=pre[x];
            t=findnum(x,y);
            if(t<=n)
            {
                pre[x]=pre[t];
                pre[t]=x;
                for(i=L[block[t]];i<=R[block[t]];i++)
                    b[i]=pre[i];
                kp(b,L[block[t]],R[block[t]]);
                for(i=L[block[x]];i<=R[block[x]];i++)
                    b[i]=pre[i];
                kp(b,L[block[x]],R[block[x]]);
            }
            else
            {
                pre[x]=last[y];
                last[y]=x;
                for(i=L[block[x]];i<=R[block[x]];i++)
                    b[i]=pre[i];
                kp(b,L[block[x]],R[block[x]]);
            }
            color[x]=y;
            for(i=L[block[x]];i<=R[block[x]];i++)
                num[i]=color[i];
            kp(num,L[block[x]],R[block[x]]);
        }
        else printf("%d\n",query(x,y));
    }
    return 0;
}


你可能感兴趣的:(二分,分块)