啟發式合並


啟發式算法是什么?

啟發式算法是基於人類的經驗和直觀感覺,對一些算法的優化。

比如說啟發式搜索\(A\)*算法。

啟發式合並是什么?

考慮一個問題:把\(n\)個總元素個數為\(m\)的數據結構合並起來(假設是線性的)。

每次合並復雜度最壞\(O(m)\),總復雜度\(O(nm)\)?顯然無法接受。

每次把個數少的合並到個數多的?復雜度\(O(min(m_1, m_2))\)

好像沒啥用?

可是我們注意到,每次合並后個數為合並前少的部分的個數的兩倍以上,每個元素最多合並\(logm​\)次,總復雜度\(O(mlogm)​\)

我們也可以啟發式合並更加高級的數據結構,如\(heap\)\(set\)\(splay\)等,復雜度\(O(mlog^2m)\)

很玄學?但這個復雜度分析是對的,而且跑的也快

例題: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;
}

在看一道新鮮出爐的聯考題:春節十二響

題意比較復雜,自己看吧$QAQ $。

考慮鏈的部分分做法,將兩條支鏈分別排序,然后從大到小加上兩邊的\(max\)即可。

那么我們就有了一個暴力做法。對每個點維護一個堆,每次像鏈那樣暴力合並即可,復雜度大概是\(O(n^2logn)\)

改成啟發式合並就可以了,每次把小的堆合並到大的堆上。時間復雜度\(O(nlog^2n)\),其實跑的很快。

#include<cstdio>
#include<vector>
#include<queue>
#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;
const int N=200005;
vector<int> G[N], Q;
priority_queue<int> q[N];
int val[N], id[N], n; long long 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 dfs(int u)
{
    for (int v: G[u])
    {
        dfs(v);
        if (q[id[u]].size()<q[id[v]].size()) swap(id[u], id[v]);
        int s=q[id[v]].size(); Q.clear();
        rep(i, 1, s)
        {
            Q.push_back(max(q[id[u]].top(), q[id[v]].top()));
            q[id[u]].pop(); q[id[v]].pop();
        }
        for (int i:Q) q[id[u]].push(i);
    }
    q[id[u]].push(val[u]);
}

int main()
{
    n=read();
    rep(i, 1, n) val[i]=read(), id[i]=i;
    rep(i, 2, n) G[read()].push_back(i);
    dfs(1);
    while (!q[id[1]].empty()) ans+=q[id[1]].top(), q[id[1]].pop();
    printf("%lld\n", ans);
    return 0;
}

本來這里還有一個題,經提醒並不是啟發式合並,故刪去


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM