1. 程式人生 > >BZOJ1483 [HNOI2009]夢幻布丁

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