• 启发式合并


    启发式算法是什么?

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

    比如说启发式搜索(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
    ", 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
    ", ans);
        return 0;
    }
    
    

    本来这里还有一个题,经提醒并不是启发式合并,故删去

  • 相关阅读:
    2019-2020-1 20191312《信息安全专业导论》第十二周学习总结
    GPG
    2019-2020-1 20191312《信息安全专业导论》第十一周学习总结
    wireshark
    ssh
    Nmap
    2019-2020-1 《python程序设计》20192428魏来 综合实践报告
    20192428 魏来 2019-2020《Python程序设计》 实验三 报告
    20192428 实验二《Python程序设计》实验报告
    20192428 实验一《Python程序设计》实验报告
  • 原文地址:https://www.cnblogs.com/ACMSN/p/10759905.html
Copyright © 2020-2023  润新知