[HNOI2009]梦幻布丁
题目描述
N个布丁摆成一行,进行M次操作.每次将某个颜色的布丁全部变成另一种颜色的,然后再询问当前一共有多少段颜色.例如颜色分别为1,2,2,1的四个布丁一共有3段颜色.
输入输出格式
输入格式:
第一行给出N,M表示布丁的个数和好友的操作次数. 第二行N个数A1,A2...An表示第i个布丁的颜色从第三行起有M行,对于每个操作,若第一个数字是1表示要对颜色进行改变,其后的两个整数X,Y表示将所有颜色为X的变为Y,X可能等于Y. 若第一个数字为2表示要进行询问当前有多少段颜色,这时你应该输出一个整数. 0
输出格式:
针对第二类操作即询问,依次输出当前有多少段颜色.
输入输出样例
输入样例#1:
4 3 1 2 2 1 2 1 2 1 2
输出样例#1:
3 1
说明
1<=n,m<=100,000; 0<Ai,x,y<1,000,000
题解:
用链表+启发式合并
1:将两个队列合并,有若干队列,总长度为n,直接合并,最坏O(N),
2:启发式合并呢?
每次我们把短的合并到长的上面去,O(短的长度)
咋看之下没有多大区别,
下面让我们看看均摊的情况:
1:每次O(N)
2:每次合并后,队列长度一定大于等于原来短的长度的两倍。
这样相当于每次合并都会让短的长度扩大一倍以上,
最多扩大logN次,所以总复杂度O(NlogN),每次O(logN)。
这题很容易搞混,启发式合并可以这么想,把x换成y可以视为把y换成x,在当前不会对答案有影响
但之后就会换错颜色,所以从把x换成y变为把y换成x之后还要记下当前颜色的实际颜色(启发式合并做了交换)
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 using namespace std; 6 int nxt[1000001],head[1000001],color[1000001],set[1000001],size[1000001],ans,first[1000001],n,m; 7 void solve(int x,int y) 8 {int i; 9 for (i=head[x];i;i=nxt[i]) 10 { 11 if (y==color[i-1]) ans--; 12 if (y==color[i+1]) ans--; 13 } 14 for (i=head[x];i;i=nxt[i]) 15 { 16 color[i]=y; 17 } 18 nxt[first[x]]=head[y]; 19 head[y]=head[x]; 20 size[y]+=size[x]; 21 head[x]=size[x]=first[x]=0; 22 } 23 int main() 24 {int i,j,opt,x,y; 25 cin>>n>>m; 26 for (i=1;i<=n;i++) 27 { 28 scanf("%d",&color[i]); 29 if (color[i]!=color[i-1]) ans++; 30 set[color[i]]=color[i]; 31 if (head[color[i]]==0) first[color[i]]=i; 32 size[color[i]]++; 33 nxt[i]=head[color[i]]; 34 head[color[i]]=i; 35 } 36 for (i=1;i<=m;i++) 37 { 38 scanf("%d",&opt); 39 if (opt==1) 40 { 41 scanf("%d%d",&x,&y); 42 if (x==y) continue; 43 if (size[set[x]]>size[set[y]]) swap(set[x],set[y]); 44 x=set[x];y=set[y]; 45 if (size[x]==0) continue; 46 solve(x,y); 47 } 48 else 49 { 50 printf("%d\n",ans); 51 } 52 } 53 }