[HNOI2009] 梦幻布丁

题目描述

解法

每种颜色的点都可以构成一个序列,而把一种颜色改成另一种颜色,实际上是将一个序列接在另一个序列后面,同时将段数量维护一下。段数量如何维护?可以看出,对于每次修改,只有被修改的点可能会产生贡献,且只与这些点前一个和后一个点有关。如果修改了之后前后的点颜色相同,就将段数减一。事实上将 (x) 并到 (y) 和将 (y) 并到 (x) 是等价的,所以每次可以贪心地选择将少的合并到多的上面,即启发式合并。而对于这个序列,用一个链表维护就可以了。每次合并,只需要修改头指针和尾指针,并将节点少的那个链表遍历一遍,暴力修改原序列颜色。这样复杂度是 (O(nlog n)) 的。

Tips

题目要求将 (x) 改成 (y),但我们有时候将 (y) 改成了 (x)。所以还要维护一个 (ref[x]),表示颜色 (x) 在当前意义下指代的是哪个颜色。

#include<stdio.h>
#define N 1000007

inline int read(){
    int x=0,flag=1; char c=getchar();
    while(c<'0'||c>'9'){if(c=='-') flag=0;c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
    return flag? x:-x;
}

inline void swap(int &x,int &y){x^=y,y^=x,x^=y;}
int n,m,h[N],t[N],ans=0,nxt[N],cnt=0,sz[N],a[N],ref[N];
int main(){
    n=read(),m=read();
    for(int i=1;i<=n;i++){
        a[i]=read(),ref[a[i]]=a[i];
        if(a[i]!=a[i-1]) ans++;
        if(!t[a[i]]) h[a[i]]=i;
        sz[a[i]]++,nxt[i]=t[a[i]],t[a[i]]=i;
    }
    while(m--){
        int opt=read();
        if(opt==1){
            int x=read(),y=read();
            if(x==y) continue;
            if(sz[ref[x]]>sz[ref[y]])
                swap(ref[x],ref[y]);
            x=ref[x],y=ref[y];
            if(!sz[x]) continue;
            for(int i=t[x];i;i=nxt[i])
                ans-=(a[i-1]==y)+(a[i+1]==y);
            for(int i=t[x];i;i=nxt[i]) a[i]=y;
            nxt[h[x]]=t[y],sz[y]+=sz[x],t[y]=t[x];
            t[x]=h[x]=sz[x]=0;
        }else printf("%d
",ans);
    }
}
原文地址:https://www.cnblogs.com/wwlwQWQ/p/14026188.html