最小路径覆盖

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

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

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

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

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

DAG的最小不相交路径覆盖

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

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

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

说的拆点这么高深,其实操作起来超级超级简单,甚至没有操作。

简单的来说,每个顶点都能当成二分图中作为起点的顶点。

答案是 n-ans(已匹配好的路径需要一个,未匹配好的路径需要一个,一共n-ans个)

例题:估计人数 

import java.util.Scanner;
 
public class Main {
    
    /*             可相交的最小路径覆盖                    */
    // n行m列
    int n, m;
    //地图
    char[][] matrix;
    //地图有多少个可以走的点
    int pointcount = 0;
    //连接表,记录每个点到任意一个点是否走到,构成一个二维矩阵
    boolean[][] con;
    //给每一个点标上序号,第一个点序号为1,跟地图位置对应
    //功能:作为索引用于转换 连接表con
    int[][] num;
    
    /*  匈牙利算法的工具    */
    //匹配表,存放匹配关系,如:一个点a匹配另一个点b,但b有被其他点匹配过,都在这个表中对应
    int[]  matchtable;
    //点b是否被匹配过
    boolean[] ismatch;
 
    
    public Main() {
        Scanner sn = new Scanner(System.in);
        n = sn.nextInt();
        m = sn.nextInt();
        matrix = new char[n][m];
        num=new int[n][m];
        sn.nextLine();
        //读取地图
        for (int i = 0; i < n; i++) {
            matrix[i] = sn.nextLine().trim().toCharArray();
            for (int j = 0; j < m; j++) {
                //记录序号,从1开始的
                if (matrix[i][j] == '1')num[i][j]=++pointcount;
            }
        }
    
        //设置连接表大小,0位置不能用,因此需要pointcount+1的行和列
        con=new boolean[pointcount+1][pointcount+1];
        //根据序号开始转换连接表
        for(int i=0;i<n-1;i++){
            for(int j=0;j<m-1;j++){
                //如果可以向下走或者向右走,说明两个点之间存在直接连接关系,对应到con上
                if(matrix[i][j]=='1'&&matrix[i][j+1]=='1')
                    con[num[i][j]][num[i][j+1]]=true;
                if(matrix[i][j]=='1'&&matrix[i+1][j]=='1')
                    con[num[i][j]][num[i+1][j]]=true;
            }
        }
            
        
        for(int k=1;k<=pointcount;k++){
            for(int i=1;i<=pointcount;i++){
                for(int j=1;j<=pointcount;j++){
                    con[i][j]|=(con[i][k]&&con[k][j]);
                }
            }
        }
        /*   匈牙利算法       */
        //开始统计匹配度
        //初始化工具
        matchtable=new int[pointcount+1];
        
        int count=0;
        //统计匹配度
        //查看有哪些点可以匹配
        for(int i=1;i<=pointcount;i++) {
            ismatch=new boolean[pointcount+1];//每次都初始化一次
            if(dfs(i)) count++;
        }
        //最小路径覆盖=原图的结点数- 所有点根据con找到的最大匹配数
        System.out.println(pointcount-count);
        
        
    }
 
    public boolean dfs(int i) {
        for(int j=1;j<=pointcount;j++) {
            if(!con[i][j])continue;//i和j无连接,跳过
            if(!ismatch[j]) {
                ismatch[j]=true;
                
                if(matchtable[j]==0||dfs(matchtable[j])) {
                    matchtable[j]=i;
                    return true;
                }
            }    
        }
        return false;
    }
 
 
    public static void main(String[] args) {
        new Main();
    
    }
}

POJ1422

//
//  main.cpp
//  POJ1422最小不想交路径覆盖
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <vector>
using namespace std;
const int N = 200 + 10;
vector<int> g[N];
int cy[N];
bool vis[N];
bool dfs(int u)
{
    for(int i=0; i<g[u].size(); ++i)
    {
        int v = g[u][i];
        if(vis[v]) continue;
        vis[v] = true;
        if(cy[v]==-1 || dfs(cy[v])){
            cy[v] = u;
            return true;
        }
    }
    return false;
}
int solve(int n)
{
    int ret = 0;
    memset(cy, -1, sizeof(cy));
    for(int i=1;i<=n;++i)
    {
        memset(vis, 0, sizeof(vis));
        ret += dfs(i);
    }
    return n - ret;
}
int main( )
{
    int t,n,m;
    int u,v;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;++i)
            g[i].clear();
        for(int i=0;i<m;++i)
        {
            scanf("%d%d",&u,&v);
            g[u].push_back(v);
        }
        
        int ans = solve(n);
        printf("%d
",ans);
    }
    return 0;
}

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

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

POJ 2594

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <vector>
using namespace std;
const int N = 500 + 10;
bool dis[N][N];
bool vis[N];
int cy[N];
void floyd(int n){
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j)
            for(int k=1;k<=n;++k)
                if(dis[i][k] && dis[k][j])//传递可达性
                    dis[i][j] = true;
}
bool dfs(int u, int n){
    for(int i=1;i<=n;++i){
        if(!vis[i] && dis[u][i]){
            vis[i] = true;
            if(cy[i]==-1 || dfs(cy[i], n)){
                cy[i] = u;
                return true;
            }
        }
    }
    return false;
}
int solve(int n){
    int cnt = 0;
    memset(cy,-1,sizeof(cy));
    for(int i=1;i<=n;++i){
        memset(vis,0,sizeof(vis));
        cnt += dfs(i, n);
    }
    return n - cnt;
}
int main(int argc, const char * argv[]) {
    int n,m;
    int a,b;
    while(scanf("%d%d",&n,&m),n+m){
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
                dis[i][j] = false;
        for(int i=1;i<=m;++i){
            scanf("%d%d",&a,&b);
            dis[a][b] = true;
        }
        floyd(n);
        int ans = solve(n);
        printf("%d
",ans);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/kayiko/p/14842324.html