学习笔记——启发式合并

我们要把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;
}
上一篇:Winform改变Textbox边框颜色(转)


下一篇:20210604