当前位置 博文首页 > 文章内容

    启发式合并

    作者: 栏目:未分类 时间:2020-07-19 14:02:19

    本站于2023年9月4日。收到“大连君*****咨询有限公司”通知
    说我们IIS7站长博客,有一篇博文用了他们的图片。
    要求我们给他们一张图片6000元。要不然法院告我们

    为避免不必要的麻烦,IIS7站长博客,全站内容图片下架、并积极应诉
    博文内容全部不再显示,请需要相关资讯的站长朋友到必应搜索。谢谢!

    另祝:版权碰瓷诈骗团伙,早日弃暗投明。

    相关新闻:借版权之名、行诈骗之实,周某因犯诈骗罪被判处有期徒刑十一年六个月

    叹!百花齐放的时代,渐行渐远!



    启发式合并

    概念

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

    作用

    可以启发式合并更加高级的数据结构,如 \(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\n", 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;
    }