启发式合并
T1 梦幻布丁
学习玄学优化,启发式合并(还是为了做大根堆)
//梦幻布丁 #include <cstdio> #include <iostream> #include <algorithm> #include <cstring> using namespace std; const int mx=1e6+1000; //数组开小了 int a[mx],head[mx],nex[mx]; int st[mx]; int sz[mx]; int n,m,len; int col[mx]; int ans; struct Node{ int to; int next; }e[mx*3]; //当前一共有多少段颜色,而不是多少种 //也就是说必须去把每个都更新 void merge(int x,int y){ for(int i=head[x];i;i=nex[i]){ if(a[i-1]==y)ans--; if(a[i+1]==y)ans--; } for(int i=head[x];i;i=nex[i]){ a[i]=y;//暴力更改每一个x } nex[st[x]]=head[y];//把x整体接到y后面 head[y]=head[x]; sz[y]+=sz[x]; head[x]=sz[x]=st[x]=0; } void Solve(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;++i){ scanf("%d",&a[i]); col[a[i]]=a[i]; if(a[i]!=a[i-1])ans++; if(head[a[i]]==0)st[a[i]]=i; sz[a[i]]++; nex[i]=head[a[i]]; head[a[i]]=i;//依据i建了个链表,比前向星还省事 } //把每种颜色建一个集合,每次更换颜色就并查集? //但这样查询就很麻烦 for(int i=1;i<=m;++i){ int q; scanf("%d",&q); if(q==2)printf("%d\n",ans); else { int x,y; scanf("%d%d",&x,&y); //我要把x颜色换成y,但是如果x颜色的个数大于y //那我就把y颜色全都换成x,但是名义上还是换成了y //col[x]是x,换成y还是x,但是如果换,把col[x]改成y,col[y]改成x //这样当我们要把y换成z,y实际上让我们换成x,那就去找col[y]=x; //当要把z换成x,肯定不能去换x,因为那里存着x和y,去换y,此时那里为空 //就相当于x.所以用一个swap //启发式合并:就这个题,你看啊,你要是莽着直接换,每次暴力去更新x的每一个数 //极限是(n-1)*m没错吧,每个元素(-1)都被更改m次 //但是如果你每次去把那个小的去改成大的,那么在改完以后 //的集合里,这个个数肯定是原来那个小的两倍以上吧,每次更新都最多只会去更新 //n/2个点,这也不对,当你换了n/2个点,下次换必定就是1或零了 // 每个元素至多被更改log n次,这么想,每次更新,一个我们更改的集合总数就要*2往上 //乘log(n)次后面就是零常更改了, 但是显然,后面肯定不用大的改 //我说这么多废话意思就是 网上都说复杂度是 n*log(n) 但我觉得实际比这个可能还要小很多 // n*m-->n*log(n) 就是这么玄学 if(x==y)continue; if(sz[col[x]]>sz[col[y]])swap(col[x],col[y]); if(sz[col[x]]==0)continue; merge(col[x],col[y]); } } } int main(){ Solve(); return 0; }
祝您,武运昌隆