• 有向无环图(DAG)的最小路径覆盖


    定理:
    柯尼希定理:二分图最小点覆盖的点数=最大匹配数。
    最小路径覆盖的边数=顶点数n-最大匹配数
    最大独立集=最小路径覆盖=顶点数n-最大匹配数

    增广路定理:用未盖点表示不与任何匹配边邻接的点,其他点位匹配点,即恰好和一条匹配边临界的点。从未盖点出发,依次经过非匹配边,匹配边,非匹配边,匹配边。。。所得到的路径称为交替路。注意,如果交替路的终点时一个未盖点,则称这条交替路位一条增广路。在增广路中,非匹配边比匹配边多一条。增广路的作用是改进匹配。如果有一条增广路,那么把此路上的匹配边和非匹配边互换,得到的匹配比刚才多一边。反过来,如果找不到增广路,则当前匹配就是最大匹配。

    查找增广路,存在增广路就交换增广路上的非匹配边和匹配边,这样会使得当前最大匹配数+1。

     

    DAG的最小路径覆盖

    定义:在一个有向图中,找出最少的路径,使得这些路径经过了所有的点。

    最小路径覆盖分为最小不相交路径覆盖和最小可相交路径覆盖。

    最小不相交路径覆盖:每一条路径经过的顶点各不相同。如图,其最小路径覆盖数为3。即1->3>4,2,5。

    最小可相交路径覆盖:每一条路径经过的顶点可以相同。如果其最小路径覆盖数为2。即1->3->4,2->3>5。

    特别的,每个点自己也可以称为是路径覆盖,只不过路径的长度是0。

    DAG的最小不相交路径覆盖

    算法:把原图的每个点V拆成VxVx和VyVy两个点,如果有一条有向边A->B,那么就加边Ax>ByAx−>By。这样就得到了一个二分图。那么最小路径覆盖=原图的结点数-新图的最大匹配数。

    证明:一开始每个点都是独立的为一条路径,总共有n条不相交路径。我们每次在二分图里找一条匹配边就相当于把两条路径合成了一条路径,也就相当于路径数减少了1。所以找到了几条匹配边,路径数就减少了多少。所以有最小路径覆盖=原图的结点数-新图的最大匹配数。

    因为路径之间不能有公共点,所以加的边之间也不能有公共点,这就是匹配的定义

    习题:POJ1422

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<sstream>
    #include<algorithm>
    #include<queue>
    #include<deque>
    #include<iomanip>
    #include<vector>
    #include<cmath>
    #include<map>
    #include<stack>
    #include<set>
    #include<functional>
    #include<memory>
    #include<list>
    #include<string>
    using namespace std;
    typedef long long LL;
    typedef unsigned long long ULL;
    const int MAXN = 510;
    /*
    如果没有边,需要n次
    每次在两个点之间加一条边,就说明需要访问的点少了一个
    同时要保证不能多次减少,也就是一个点最多匹配一次
    二分图最大匹配
    */
    int uN, vN;//u,v的数目,使用前面必须赋值
    int g[MAXN][MAXN];//邻接矩阵
    int linker[MAXN];
    bool used[MAXN];
    bool dfs(int u)
    {
        for (int v = 0; v < vN; v++)
            if (g[u][v] && !used[v])
            {
                used[v] = true;
                if (linker[v] == -1 || dfs(linker[v]))
                {
                    linker[v] = u;
                    return true;
                }
            }
        return false;
    }
    int hungary()
    {
        int res = 0;
        memset(linker, -1, sizeof(linker));
        for (int u = 0; u < uN; u++)
        {
            memset(used, false, sizeof(used));
            if (dfs(u))res++;
        }
        return res;
    }
    int main()
    {
        int T, n, m;
        ios::sync_with_stdio(0);
        cin >> T;
        while (T--)
        {
            memset(g, 0, sizeof(g));
            cin >> n;
            uN = vN = n;
            cin >> m;
            while (m--)
            {
                int f, t;
                cin >> f >> t;
                f--, t--;
                g[f][t] = 1;
            }
            cout << n - hungary() << endl;
        }
    }

    DAG的最小可相交路径覆盖

    算法:先用floyd求出原图的传递闭包,即如果a到b有路径,那么就加边a->b。然后就转化成了最小不相交路径覆盖问题。

    证明:为了连通两个点,某条路径可能经过其它路径的中间点。比如1->3->4,2->4->5。但是如果两个点a和b是连通的,只不过中间需要经过其它的点,那么可以在这两个点之间加边,那么a就可以直达b,不必经过中点的,那么就转化成了最小不相交路径覆盖。

    题目:POJ2594

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<sstream>
    #include<algorithm>
    #include<queue>
    #include<deque>
    #include<iomanip>
    #include<vector>
    #include<cmath>
    #include<map>
    #include<stack>
    #include<set>
    #include<functional>
    #include<memory>
    #include<list>
    #include<string>
    using namespace std;
    typedef long long LL;
    typedef unsigned long long ULL;
    const int MAXN = 510;
    /*
    如果没有边,需要n次
    每次在两个点之间加一条边,就说明需要访问的点少了一个
    同时要保证不能多次减少,也就是一个点最多匹配一次
    二分图最大匹配
    
    新增条件:每一条路径经过的顶点可以相同
    用floyd传递闭包然后建边
    */
    int uN, vN;//u,v的数目,使用前面必须赋值
    int g[MAXN][MAXN];//邻接矩阵
    int linker[MAXN];
    bool used[MAXN];
    bool dfs(int u)
    {
        for (int v = 0; v < vN; v++)
            if (g[u][v] && !used[v])
            {
                used[v] = true;
                if (linker[v] == -1 || dfs(linker[v]))
                {
                    linker[v] = u;
                    return true;
                }
            }
        return false;
    }
    int hungary()
    {
        int res = 0;
        memset(linker, -1, sizeof(linker));
        for (int u = 0; u < uN; u++)
        {
            memset(used, false, sizeof(used));
            if (dfs(u))res++;
        }
        return res;
    }
    void floyd(int n)
    {
        for (int k = 0; k < n; k++)
        {
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < n; j++)
                    if (g[i][k] && g[k][j])
                        g[i][j] = 1;
            }
        }
    }
    int main()
    {
        int T, n, m;
        ios::sync_with_stdio(0);
        while (scanf("%d%d", &n, &m), n + m)
        {
            if (m == 0)
            {
                cout << n << endl;
                continue;
            }
            memset(g, 0, sizeof(g));
            uN = vN = n;
            while (m--)
            {
                int f, t;
                cin >> f >> t;
                f--, t--;
                g[f][t] = 1;
            }
            floyd(n);
            cout << n - hungary() << endl;
        }
    }
  • 相关阅读:
    11 对象的构造
    10 问题分析一
    9 新型的类型转换
    8 C++ 中的新成员
    7 函数重载
    6 函数参数的扩展
    5 内联函数
    查找算法总结Java实现
    九大排序算法Java实现
    帝国cms所有一级栏目遍历,如果有子栏目的话,遍历出来
  • 原文地址:https://www.cnblogs.com/joeylee97/p/7525434.html
Copyright © 2020-2023  润新知