BZOJ1483 [HNOI2009]梦幻布丁

题目描述:

N个布丁摆成一行,进行M次操作.

每次将某个颜色的布丁全部变成另一种颜色的,然后再询问当前一共有多少段颜色.

例如颜色分别为1,2,2,1的四个布丁一共有3段颜色.

 

题解:

链表加启发式合并。

对每个颜色开个链表记录这个颜色每个布丁的位置,然后启发式合并链表,每次小的往大的合并,设小的链表大小x,

每次合并复杂度是O(x),维护答案复杂度是O(x),合并后规模至少是2x,算一下复杂度就知道是O(nlogn)的怎么用启发式合并呢?

要把链表长度小的接在链表长度大的后面,才能做到nlogn。因为把链表长度小的接在大的后面,新链表长度一定>=原长度小的链表的长度的两倍,

最多变长logn次,所以均摊下来每次修改效率O(logn),总复杂度为O(nlogn)。

那交换之后颜色换反了怎么办?

记录一下每种颜色真实颜色是什么,如果一次染色是将一个大链表染向一个小链表,那就将两种颜色的真实颜色交换,仍然将小链表往大链表合并即可。

附上代码:

#include<cstdio>
int n,m,a[100001],f[1000001],s[1000001],k,c,d,ans,head[1000001],next[1000001],l[1000001];
void merge(int x,int y)
{
    for(int i=head[x];i;i=next[i])
    {
        if(a[i+1]==y)
            ans--;
        if(a[i-1]==y)
            ans--;    
    }
    for(int i=head[x];i;i=next[i])
        a[i]=y;
    next[l[x]]=head[y];
    head[y]=head[x];
    s[y]+=s[x];
    s[x]=0;
    head[x]=0;
    l[x]=0;    
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        f[a[i]]=a[i];
        if(a[i]!=a[i-1])
            ans++;
        if(head[a[i]]==0)
            l[a[i]]=i;
        s[a[i]]++;
        next[i]=head[a[i]];
        head[a[i]]=i;
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d",&k);
        if(k==1)
        {
            scanf("%d%d",&c,&d);
            if(c==d)
                continue;
            if(s[f[c]]>s[f[d]])
            {
                int z=f[d];
                f[d]=f[c];
                f[c]=z;
            }
            if(s[f[c]]==0)
                continue;
            s[f[d]]+=s[f[c]];
            s[f[c]]=0;
            merge(f[c],f[d]);
        }
        else
            printf("%d\n",ans);
    }    
    return 0;
}

 

posted @ 2018-10-18 16:30  jiangminghong  阅读(180)  评论(0编辑  收藏  举报