启发式合并
思想
启发式合并就是在合并的时候将 \(size\) 小的合并到 \(size\) 大的里面去。
可以用链表维护,也可以用 \(vector\)。
我们可以分析一下每个元素被 \(push\) _ \(back()\) 了多少次。
一个元素放到另一个集合中会被 \(push\) _ \(back()\) 一次,每次合并后大集合元素个数为合并前少的元素的个数的两倍以上,每个元素最多合并 \(\log m\) 次,总复杂度 \(O(m\log m)\)。(\(m\) 是元素个数)
我们也可以启发式合并更加高级的数据结构,如 \(heap\),\(set\),\(splay\) 等,复杂度\(O(m \log^2m)\)。
是不是很简单!
例题
先更新一下线段树合并做法(
按颜色建立线段树,维护坐标,如果同一个颜色的两个位置相邻,就将两段合并,段的合不增加。
code:
const int N=1e5+10;
const int M=1e6+10;
int n,m,op,x,y,ans,tot,a[N],rt[M],sum[M<<4];
struct node{
int l,r,lv,rv;
}t[M<<4];
/*
可以发现 一段颜色不会被破坏
只能被合并或者改变
比如 [1 1][2 2][1]
当 2=>1 时 [1 1 1 1 1]
sol1: 线段树合并
每个颜色建立线段树 维护出现的位置信息
*/
void push_up(int k) {
t[k].lv=t[t[k].l].lv?t[t[k].l].lv:t[t[k].r].lv;
t[k].rv=t[t[k].r].rv?t[t[k].r].rv:t[t[k].l].rv;
sum[k]=sum[t[k].l]+sum[t[k].r]-(t[t[k].l].rv+1==t[t[k].r].lv);
}
void insert(int &now,int l,int r,int w){
if(!now) now=++tot;
if(l==r) {t[now].lv=t[now].rv=w;sum[now]=1;return ;}
int mid=(l+r)>>1;
if(w<=mid) insert(t[now].l,l,mid,w);
if(w>mid) insert(t[now].r,mid+1,r,w);
push_up(now);
}
void merge(int &x,int y,int l,int r) {
if(!x||!y) {x+=y; return ;}
if(l==r) {t[x].lv=t[x].rv=l;sum[x]=1;return ;}
int mid=(l+r)>>1;
merge(t[x].l,t[y].l,l,mid);
merge(t[x].r,t[y].r,mid+1,r);
push_up(x);
}
signed main(){
n=read(); m=read();
for(int i=1;i<=n;++i) {
a[i]=read();
ans-=sum[rt[a[i]]];
insert(rt[a[i]],1,n,i);
ans+=sum[rt[a[i]]];
}
for(int i=1;i<=m;++i) {
op=read();
if(op==1) {
x=read(); y=read();
if(x==y) continue;
ans-=(sum[rt[x]]+sum[rt[y]]);
merge(rt[y],rt[x],1,n);
rt[x]=0;
ans+=sum[rt[y]];
}
else {
cout<<ans<<'\n';
}
}
return 0;
}
启发式合并做法:
未完待续(你先别急,我先把这个题写出来再说。

浙公网安备 33010602011771号