前言:
这是Educational Codeforces Round 119 (Rated for Div. 2)的第五题,
这个题我最开始使用从后往前的方法写的,复杂度
O
(
n
)
O(n)
O(n)。
交完以后我看看了题解,题解提供了两种办法,第二种是用启发式合并(英文名叫 small to large method),看到这我别提有多激动了,因为自从学了启发式合并以后一直没有找到适合练手的题目。
那我就通过这个题来好好梳理一下启发式合并吧。
例题题面:
You have an array of integers (initially empty).
You have to perform q queries. Each query is of one of two types:
“1 x” — add the element x to the end of the array;
“2 x y” — replace all occurrences of x in the array with y.
Find the resulting array after performing all the queries.
Input
The first line contains a single integer q (1≤q≤5⋅105) — the number of queries.
Next q lines contain queries (one per line). Each query is of one of two types:
“1 x” (1≤x≤5⋅105);
“2 x y” (1≤x,y≤5⋅105).
It’s guaranteed that there is at least one query of the first type.
Output
In a single line, print k integers — the resulting array after performing all the queries, where k is the number of queries of the first type.
啥是启发式合并
启发式合并,就是高效的、从小到大的合并。它给人的直接感觉很弱,不像线段树什么的能让你瞬间体会到算法的精妙性。实际上,启发式合并可以将很多
n
2
n^2
n2的复杂度降级为
n
l
o
g
n
nlogn
nlogn,如果敏锐地洞察出一个题可以启发式合并,我们的算法性能将得到极大的提升。
启发式合并怎么做
以此题为例,我们为每个数开一个线性表(最好用vector)。执行操作
1
1
1时,若x是第n个追加的元素,则将n放入第
x
x
x个线性表(也就是说一个线性表里面装了这个值出现的所有位置)。执行操作
2
2
2时,我们要将x出现的位置改为
y
y
y,也就是要把第
x
x
x个线性表全部倒入第
y
y
y个线性表。如果暴力来处理的话,必定会超时。那么怎么优化呢?这里无非是对两个线性表的一个合并,我们利用启发式合并的策略,将较小的那个线性表倒入较大的线性表即可(此处的大小指元素个数)。合并完之后,如果表名不一致颠到,交换一下即可(vector的交换机制是改指针,所以复杂度是
O
(
1
)
O(1)
O(1)的)。需要注意的是,当
x
=
y
x=y
x=y时,应当直接跳过。
启发式合并的复杂度
本算法的主要时间消耗在于合并两个线性表(或者说是集合)。
每次合并操作,我们都将小集合倒入大集合,只有小集合中的元素发生了移动,而且最终集合的大小至少是小集合的两倍。
假设
a
a
a被移动了
b
b
b次,也就是说
a
a
a所在的集合的大小发生了b次倍增(有可能比倍增还大)。所以
n
>
=
2
b
n>=2^b
n>=2b,也就是说
b
<
=
l
o
g
n
b<=logn
b<=logn。
因此所有元素移动的总次数是
n
l
o
g
n
nlogn
nlogn。
仔细阅读我们发现,在推导这个复杂度的时候,我们分析的是移动次数最多的那个元素的最多移动次数,所以这个算法的效率往往比我们预期的还要快很多。
例题代码:
#include<bits/stdc++.h>
#define endl '\n'
using namespace std;
vector<int>v[500010];
int a[500010];
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
int q,num=0;cin>>q;
while(q--){
int op,x,y;
cin>>op;
if(op==1){
cin>>x;
v[x].push_back(++num);
}
else{
cin>>x>>y;
if(x==y) continue;
if(v[x].size()>v[y].size()){
for(auto i:v[y]){
v[x].push_back(i);
}
v[y].clear();
swap(v[x],v[y]);
}
else{
for(auto i:v[x]){
v[y].push_back(i);
}
v[x].clear();
}
}
}
for(int i=1;i<=500000;i++){
for(auto j:v[i]){
a[j]=i;
}
}
for(int i=1;i<=num;i++){
cout<<a[i]<<' ';
}cout<<endl;
return 0;
}