启发式合并

Posted jasony

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了启发式合并相关的知识,希望对你有一定的参考价值。

启发式合并

概念

启发式算法是基于人类的经验和直观感觉,对一些算法的优化。

作用

可以启发式合并更加高级的数据结构,如 (heap,~set,~splays)

复杂度计算

每次把个数少的合并到个数多的?复杂度 (O(min(m1,m2)))

可是我们注意到,每次合并后个数为合并前少的部分的个数的两倍以上,每个元素最多合并 (logm) 次,总复杂度 (O(mlogm))

当合并 (heap,~set,~splays) 等,复杂度 (O(mlog2m))(作者太弱,不会证明)

例题1

[HNOI2009] 梦幻布丁

思路

对于每一个颜色,建一条链表。然后染色就是把链短的合并到链长的。

需要注意细节,如果把2染成3,但2的链比3的长,就需要把3的合并到2上。但是现在本应属于3的链在2上,就需要记一个该颜色的链现在在哪个颜色上,即是代码中的 (now) 数组。

代码
#include<cstdio>
#define rep(i, a, b) for (register int i=(a); i<=(b); ++i)
#define per(i, a, b) for (register int i=(a); i>=(b); --i)
using namespace std;
void swap(int &x, int &y){x^=y^=x^=y;}
const int N=1000005;
int head[N], nxt[N], col[N], now[N], cnt[N], st[N], ans;

inline int read()
{
    int x=0,f=1;char ch=getchar();
    for (;ch<‘0‘||ch>‘9‘;ch=getchar()) if (ch==‘-‘) f=-1;
    for (;ch>=‘0‘&&ch<=‘9‘;ch=getchar()) x=(x<<1)+(x<<3)+ch-‘0‘;
    return x*f;
}

void merge(int x, int y)
{
    cnt[y]+=cnt[x]; cnt[x]=0;
    for (int i=head[x]; i; i=nxt[i])
    {
        if (col[i+1]==y) ans--;
        if (col[i-1]==y) ans--;
    }
    for (int i=head[x]; i; i=nxt[i]) col[i]=y;
    nxt[st[x]]=head[y]; head[y]=head[x];
    head[x]=st[x]=cnt[x]=0;
}

int main()
{
    int n=read(), m=read();
    rep(i, 1, n)
    {
        col[i]=read(); now[col[i]]=col[i];
        if (col[i]^col[i-1]) ans++;
        if (!head[col[i]]) st[col[i]]=i;
        cnt[col[i]]++; nxt[i]=head[col[i]]; head[col[i]]=i;
    }
    rep(i, 1, m)
    {
        int opt=read();
        if (opt==2) printf("%d
", ans);
        else
        {
            int x=read(), y=read();
            if (x==y) continue;
            if (cnt[now[x]]>cnt[now[y]]) swap(now[x], now[y]);
            x=now[x]; y=now[y];
            if (cnt[x]) merge(x, y);
        }
    }
    return 0;
}

例题2

[十二省联考2019]春节十二响

思路

考虑一条链,显然你是把两个链分别的最大值放在一起,次大值放在一起,等等

那么如果有多个链呢?你就把第一个链和第二个链按上面的操作,得到的新的结果再和第三个链合并...

代码
#include<iostream>
#include<cstdio>
#include<queue>
#include<vector>
#include<algorithm>
#define ll long long

using namespace std;
const int maxn=2e5+5;
int a[maxn],id[maxn],tot,tp[maxn];

priority_queue<int> q[maxn];

inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){if(ch==‘-‘) f=-1;ch=getchar();}
	while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-‘0‘;ch=getchar();}
	return x*f;
}

struct Edge{
	int to,next;
}e[maxn<<1];

int head[maxn],cnt;
void add(int u,int v){
	e[++cnt].to=v;
	e[cnt].next=head[u];
	head[u]=cnt;
}

inline void dfs(int now,int fa){
	id[now]=++tot;
	for(int i=head[now];i;i=e[i].next)
	{
		int v=e[i].to;
		if(v==fa) continue;
		dfs(v,now);
		if(q[id[now]].size()<q[id[v]].size()) swap(id[now],id[v]);
		int size=q[id[v]].size();
		for(int j=1;j<=size;j++)
		{
			tp[j]=max(q[id[now]].top(),q[id[v]].top());
			q[id[now]].pop();
			q[id[v]].pop();
		}
		for(int j=1;j<=size;j++) q[id[now]].push(tp[j]);
	}
	q[id[now]].push(a[now]);
}

int main(){
	int n;
	n=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	for(int i=2;i<=n;i++)
	{
		int f;
		f=read();
		add(i,f);
		add(f,i);
	}
	dfs(1,0);
	ll ans=0;
	while(q[id[1]].size()) ans+=q[id[1]].top(),q[id[1]].pop();
	printf("%lld",ans);
	return 0;
}

以上是关于启发式合并的主要内容,如果未能解决你的问题,请参考以下文章

bzoj2733: [HNOI2012]永无乡(splay+启发式合并/线段树合并)

#线段树合并树上启发式合并#CF600E Lomsat gelral

BZOJ 2888 资源运输(启发式合并LCT)

luoguP5161 WD与数列 后缀自动机+线段树合并+启发式合并

数据结构 - 启发式合并

HDU6191 Query on A Tree (01字典树+启发式合并)