启发式合并 O(nlogn)
对于两个集合A、B,当我们想把它们合并为一个整体,那么就把小的集合放进大的集合里去。
对于多个集合也适用:A、B、C三个集合,选择其中最大的那个集合,其余的两个集合放入其中。
证明时间复杂度
我们可以考虑有三个集合时候,选择最大的那个集合,其余的两个集合放入最大的那个中,成为一个整体。
如A.size() = 4, B.size() = 2, C.size()=3
, 那么把B,C里的每一个元素逐个放入A中,时间复杂度5。
从每一个元素的贡献来考虑,B中每一个元素,它们每次贡献给时间复杂度时,所在的集合大小至少变大两倍,所以每个元素最多参与logn次的时间复杂度,因此全局时间复杂度O(nlogn)。
这种机制 对于树上节点集合的合并、链条合并都很好用。
比如每一个树上的节点代表了一个集合,集合存了所有子节点的特征;那么通过深度遍历的时候就能先拿到size最大的子树集合,然后把其他的集合放入其中,实现合并。
Educational Codeforces Round 132 (Rated for Div. 2)Problem - E - Codeforces
例题
#include<iostream>
#include<set>
#include<vector>
using namespace std;
const int maxn = 1e6 + 222;
int ori[maxn];
vector<int> e[maxn];
set<int>se[maxn];
int totop[maxn];
int ans = 0;
void dfs(int nowid, int fa, int nowval){
totop[nowid] = nowval ^ ori[nowid];
int mxson = 0;
for(int d : e[nowid]){
if(d ==fa)
continue;
dfs(d, nowid, totop[nowid]);
if(se[d].size() > se[mxson].size()){
mxson = d;
}
}
if(mxson == 0){
se[nowid].insert(totop[nowid]);
return;
}
for(int d : e[nowid]){
if(d ==fa)
continue;
if(se[d].count(nowval)){
ans++;
se[nowid].clear();
return;
}
}
se[nowid].swap(se[mxson]);
for(int d : e[nowid]){
if(d == mxson || d == fa)
continue;
for(int v : se[d]){
if(se[nowid].count(v ^ ori[nowid])){
ans++;
se[nowid].clear();
return;
}
}
for(int v : se[d]){
se[nowid].insert(v);
}
}
se[nowid].insert(totop[nowid]);
return;
}
void solve(){
int n;
scanf("%d", &n);
for(int i = 1;i <= n; i++){
scanf("%d", &ori[i]);
}
for(int i = 1; i < n; i++){
int a, b;
scanf("%d %d", &a, &b);
e[a].push_back(b);
e[b].push_back(a);
}
dfs(1, 1, 0);
cout<<ans<<endl;
}
int main(){
solve();
}