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; }