1483: [HNOI2009]梦幻布丁
Time Limit: 10 Sec Memory Limit: 64 MBSubmit: 1818 Solved: 761
[Submit][Status][Discuss]
Description
N个布丁摆成一行,进行M次操作.每次将某个颜色的布丁全部变成另一种颜色的,然后再询问当前一共有多少段颜色.例如颜色分别为1,2,2,1的四个布丁一共有3段颜色.
Input
第 一行给出N,M表示布丁的个数和好友的操作次数. 第二行N个数A1,A2...An表示第i个布丁的颜色从第三行起有M行,对于每个操作,若第一个数字是1表示要对颜色进行改变,其后的两个整数X,Y表 示将所有颜色为X的变为Y,X可能等于Y. 若第一个数字为2表示要进行询问当前有多少段颜色,这时你应该输出一个整数. 0
Output
针对第二类操作即询问,依次输出当前有多少段颜色.
Sample Input
1 2 2 1
2
1 2 1
2
Sample Output
1
【思路】
链表+启发式合并。
每种颜色设一个链表,并记下段数ans。
对于修改,先假设不变,如果左,右不一样则ans--,然后修改颜色值,再遍历一次,如果不一样则ans++,这样就只有因为颜色相同而并成一块的会ans—了。具体修改成哪一个颜色对题目并没有影响,所以采用启发式合并,每次将颜色数少的设为颜色数多的,合并后的总长度一定大于较短的两倍,我们每次都是操作较短的,每次合并长度都会扩大两倍以上最多扩大logn次,时间复杂度为O(nlogn)。
【代码】
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 6 const int M = 1e6+100; 7 8 int n,m,a[M],num[M],next[M],front[M],p[M],ans=0; 9 10 int main() { 11 scanf("%d%d",&n,&m); 12 memset(front,-1,sizeof(front)); 13 for(int i=1;i<=n;i++){ 14 scanf("%d",&a[i]); 15 num[a[i]]++; 16 } 17 for(int i=1;i<=n;i++){ 18 next[i]=front[a[i]]; 19 front[a[i]]=i; 20 } 21 for(int i=1;i<=n;i++) 22 if(a[i]!=a[i-1]) ans++; 23 for(int i=1;i<=M-1;i++) p[i]=i; 24 for(int j=1;j<=m;j++) { 25 int op,x,y; 26 scanf("%d",&op); 27 if(op==2) 28 printf("%d\n",ans); 29 else { 30 scanf("%d%d",&x,&y); 31 if(x==y) continue; 32 if(num[p[x]]>num[p[y]]) swap(p[x],p[y]); 33 x=p[x]; y=p[y]; 34 if(num[x]==0) continue; 35 for(int i=front[x];i!=-1;i=next[i]){ 36 if(a[i]!=a[i-1]) ans--; 37 if(a[i]!=a[i+1]&&i!=n) ans--; 38 } 39 for(int i=front[x];i!=-1;i=next[i]) a[i]=y; 40 int head; 41 for(int i=front[x];i!=-1;i=next[i]) { 42 if(a[i]!=a[i-1]) ans++; 43 if(a[i]!=a[i+1]&&i!=n) ans++; 44 head=i; 45 } 46 num[y]+=num[x]; num[x]=0; 47 next[head]=front[y]; front[y]=front[x]; 48 front[x]=-1; 49 } 50 } 51 return 0; 52 }