学习笔记——启发式合并
我们要把N个集合,总共M个元素合并成一个大集合。
很容易得出,最坏的情况下需要合并N次,每次合并M个元素,也就是O(MN)的时间复杂度。
同样的问题,如果我们把小的往大的里合,是不是就快很多?
那么他的复杂度是多少呢?
考虑一个集合,被合并一次后他的大小至少乘2,那么他被合并的次数至多为\(logn\)次,对于集合内的每一个元素至多会合并\(logn\)次,那么总共会合并\(O(nlogn)\)
例题:loj dp一般看规律
对于将一个元素换成另一种颜色的操作,不难抽象成把两个元素合并,这样最多会进行\(logn\)次有效操作
启发式合并代码:
void merge(int x,int y){
// cout<<"x = "<<x<<" "<<y<<endl;
bool flag = 0;
if (s[x].size() > s[y].size()) swap(x,y),flag = 1;
for (auto i = s[x].begin();i != s[x].end();i++){
auto lst = s[y].lower_bound(*i);
if (lst != s[y].end()) ans = min(ans,*lst-*i);
auto pre = s[y].lower_bound(*i);
if (pre != s[y].begin()) pre--,ans = min(ans,*i-*pre);
}
// cout<<"size = "<<s[x].size()<<" "<<s[y].size()<<endl;
for(auto i = s[x].begin();i != s[x].end();i++) s[y].insert(*i);
s[x].clear();
if (flag) swap(s[x],s[y]);
}
约定x比y小,然后把x合并到y上,然后把小的清空,最终保证y是合并后的集合
例题2:CF600E Lomsat gelral
这道题我们可以遍历整棵树,并记录每种颜色出现几次
但是每做完一棵子树就需要清空,以免对其兄弟造成影响。
而这样做它的祖先时就要把它重新搜一遍,浪费时间
但是我们发现,对于每个节点v,最后一棵子树是不用清空的,因为做完那棵子树后可 以把其结果直接加入v的答案中。
选哪棵子树呢?当然是所含节点最多的一棵咯,我们称之为“重儿子”
考虑一个点会被计算几次,他可以分为两个部分,dfs和暴力统计答案
对于前者每个点只会被经过1次,对于后者,因为从一个点向上跳最多有log个轻边,所以会被经过\(logn\)次,这样n个点复杂度为\(nlogn\)
代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <vector>
#define int long long
#define B cout<<"Breakpoint"<<endl;
#define O(x) cout<<#x<<" "<<x<<endl;
#define o(x) cout<<#x<<" "<<x<<" ";
using namespace std;
int read(){
int x = 1,a = 0;char ch = getchar();
while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
return x*a;
}
const int maxn = 1e5+10;
int n,col[maxn];
struct node{
int to,nxt;
}ed[maxn << 1];
int head[maxn],tot;
int sum,maxx;
void add(int u,int to){
ed[++tot].to = to;
ed[tot].nxt = head[u];
head[u] = tot;
}
int siz[maxn],son[maxn];
void dfs_init(int x,int fa){
siz[x] = 1;
for (int i = head[x];i;i = ed[i].nxt){
int to = ed[i].to;
if (to == fa) continue;
dfs_init(to,x);
siz[x] += siz[to];
if (siz[son[x]] < siz[to]) son[x] = to;
}
}
int c[maxn],ans[maxn];
void getans(int x,int fa,int pos){
c[col[x]]++;
if (c[col[x]] > maxx) maxx = c[col[x]],sum = col[x];
else if (c[col[x]] == maxx) sum += col[x];
for (int i = head[x];i;i = ed[i].nxt){
int to = ed[i].to;
if (to == fa||to == pos) continue;
getans(to,x,pos);
}
}
void clear(int x,int fa){
c[col[x]]--;
for (int i = head[x];i;i = ed[i].nxt){
int to = ed[i].to;
if (to == fa) continue;
clear(to,x);
}
}
void dfs(int x,int fa){
for (int i = head[x];i;i = ed[i].nxt){
int to = ed[i].to;
if (to == fa||to == son[x]) continue;
dfs(to,x),clear(to,x),sum = maxx = 0;
}
if (son[x]) dfs(son[x],x);
getans(x,fa,son[x]);
ans[x] = sum;
}
signed main(){
n = read();
for (int i = 1;i <= n;i++) col[i] = read();
for (int i = 1;i < n;i++){
int x = read(),y = read();
add(x,y),add(y,x);
}
dfs_init(1,0);dfs(1,0);
for (int i = 1;i <= n;i++) printf("%lld ",ans[i]);
return 0;
}