DAG最小路径点覆盖,最小路径可重复覆盖,详解

news/2024/7/15 17:20:38 标签: c++, 数据结构, 图论

文章目录

    • 前言
    • 有向无环图的最小路径点覆盖
      • 概念
      • 拆点二分图
      • 定理
        • **证明**
      • 最小路径可重复覆盖
        • 解决策略
        • 代码实现
    • OJ练习

前言

关于二分图:二分图及染色法判定

关于二分图最大匹配:二分图最大匹配——匈牙利算法详解

关于二分图带权最大完备匹配:二分图带权最大匹配-KM算法详解


有向无环图的最小路径点覆盖

概念

给定一张有向无环图,要求用尽量少的不相交的简单路径,覆盖有向无环图的所有顶点(也就是每个顶点恰好被覆盖一次)。这个问题被称为有向无环图的最小路径点覆盖,简称**“最小路径覆盖”**。

拆点二分图

设原来的有向无环图为G = (V , E), n = |V|。 把G中的每个点x拆成编号为x和x+n的两个点。建立一张新的二分图,1~n作为二分图左部点,n + 1 ~ 2n作为二分图右部点,对于原图的每条有向边(x,y), 在二分图的左部点x与右部点y+n之间连边。最终得到的二分图称为G的拆点二分图,记为G’。例如:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

定理

有向无环图G的最小路径点覆盖包含的路径条数,等于n减去拆点二分图G’的最大匹配数。

证明

在有向无环图G= (V,E)的最小路径覆盖中,对于任意的x ∈ V,因为路径不相交,所以x的入度和出度都不超过1。因为每个节点都被覆盖,所以x的入度和出度至少有一个是1
因此,最小路径覆盖中的所有边,在拆点二分图G‘中构成一组匹配。最小路径覆盖中每条边(x,y) 的起点x与拆点二分图每条匹配边(x,y+n)的左部点x是一一对应的
特别地,对于每条路径的终点t,因为t没有出边,所以在二分图中,t匹配失败。即路径的终点和拆点二分图左部的非匹配点是一一对应的。

故有:路径覆盖包含的路径条数最少

等价于 路径的终点数(出度为0的点数)最少

等价于 拆点二分图左部非匹配点最少

等价于 拆点二分图匹配数最大

G的最小路径覆盖的路径数等于n减去拆点二分图G‘的最大匹配数
证毕。

最小路径可重复覆盖

给定一张有向无环图,要求用尽量少的可相交的简单路径,覆盖有向无环图的所有顶点(也就是一个节点可以被覆盖多次)。这个问题被称为有向无环图的最小路径可重复点覆盖

解决策略

在最小路径可重复点覆盖中,若两条路径:…→u→p→v→…和…→x→p→y→…在点p相交,则我们在原图中添加一条边(x,y),让第二条路径直接走x→y,就可以避免重复覆盖点p。

进一步地,如果我们把原图中所有间接连通的点对x,y直接连上有向边(x,y),那么任何“有路径相交的点覆盖”一定都能转化成“没有路径相交的点覆盖”。实际上,转化后的拆点二分图的未匹配左部点仍为最小可相交路径数目,例如:外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

综上所述,有向无环图G的最小路径可重复点覆盖等价于先对有向图传递闭包得到有向无环图G’,再在G’上求一般的(路径不可相交的)最小路径点覆盖

代码实现

#define int long long
#define N 205

int n, m, match[N]{0}, ans = 0;
bool vis[N], g[N][N]{0};//邻接矩阵存图
//匈牙利
bool dfs(int u)
{
    for (int v = 1; v <= n; v++)
        if (!vis[v] && g[u][v])
        {
            vis[v] = true;
            if (!match[v] || dfs(match[v]))
            {
                match[v] = u;
                return true;
            }
        }
    return false;
}

//main
    // Floyd求传递闭包
    for (int i = 1; i <= n; i++)
        g[i][i] = true;

    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                g[i][j] |= g[i][k] && g[k][j];

    for (int i = 1; i <= n; i++)
        g[i][i] = false;

    for (int i = 1; i <= n; i++)
        memset(vis, 0, sizeof(vis)), ans += dfs(i);

    cout << n - ans;

OJ练习

379. 捉迷藏 - AcWing题库

这道题目的最大藏身点数目就是给定的有向无环图的最小路径可重复覆盖数目。

设最大藏身点集合为hide,最小路径可重复覆盖为path,往证hide = path:

显然藏身点不能在同一条路径上,而path包含了所有的点,所以每条path最多选一个藏身点,故|hide| <= |path|

如果我们能在path上构造出|path|个藏身点,那么就得证了,因为|hide|是最大藏身点数目,而最大又不超过|path|。

求path很容易,我们即然能求出最小覆盖,自然能还原出路径:

  1. 对于每条path的终点即为拆点二分图非匹配点,这个显然很好求
  2. 非匹配点x的右部拆点为x + n,那么通过match[x + n]可以找到x在path的邻接点y,同样的方法一直往下进行,可以还原出path

问题在于如何从每条path上选出一个藏身点:

  1. 记path终点集合为E,从E发出的边往下走,访问到的节点集合为next(E)
  2. 若E ∩ next(E) = Ø,即藏身点之间无路径,那么E 可以作为 hide
  3. 否则,对于E ∩ next(E)中的 每个节点e,沿着路径往回走,总能找到e’ ∉ next(E),否则path数目会减少,和最小覆盖矛盾
  4. 找到e’后删除e,再重复3,直到E ∩ next(E) = Ø,此时E即为符合条件的hide

证毕

AC代码如下:

#include <iostream>
#include <string>
#include <cstring>
#include <algorithm>
#include <climits>
#include <cmath>
#include <functional>
using namespace std;
#define int long long
#define N 205

int n, m, match[N]{0}, ans = 0;
bool vis[N], g[N][N]{0};

bool dfs(int u)
{
    for (int v = 1; v <= n; v++)
        if (!vis[v] && g[u][v])
        {
            vis[v] = true;
            if (!match[v] || dfs(match[v]))
            {
                match[v] = u;
                return true;
            }
        }
    return false;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    //freopen("in.txt", "r", stdin);

    cin >> n >> m;
    for (int i = 0, a, b; i < m; i++)
    {
        cin >> a >> b;
        g[a][b] = true;
    }
    for (int i = 1; i <= n; i++)
        g[i][i] = true;

    // 传递闭包
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                g[i][j] |= g[i][k] && g[k][j];

    for (int i = 1; i <= n; i++)
        g[i][i] = false;

    for (int i = 1; i <= n; i++)
        memset(vis, 0, sizeof(vis)), ans += dfs(i);

    cout << n - ans;
    return 0;
}

http://www.niftyadmin.cn/n/5330268.html

相关文章

canvas绘制美队盾牌

查看专栏目录 canvas示例教程100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…

利用python将Excel文件拆分为多个CSV

目录 一、准备工作 二、拆分Excel文件为多个CSV 1、读取Excel文件&#xff1a; 2、确定要拆分的列&#xff1a; 3、创建空的字典来存储CSV文件&#xff1a; 4、循环遍历数据并根据类别拆分&#xff1a; 5、打印或返回CSV文件名字典&#xff1a; 6、保存CSV到特定目录&a…

ACL【新华三与华为的区别】

【解释】acl简单点解释就是&#xff0c;一套根据需求而设置的规则 【背景】 192.168.1.0/24 网段不允许访问 192.168.2.0/24 网段&#xff0c;要求使用基本 ACL 实现20_1 可以访问 20_6 的 TELNET 服务&#xff0c;但不能访问 FTP 服务 【操作步骤】 {易混点 }&#xff1a;1. …

重启阿里云ESC服务器后,数据库与jar包外面无法访问bug

bug 重启了服务器&#xff0c;发现从外面无法连接数据库 原因 使用firewall-cmd --list-all命令查看服务器防火墙的配置&#xff0c;发现没有开启3306端口的开放&#xff0c;虽然我们在安全组设置3306端口但是防火墙没有开启&#xff0c;外面是依然无法访问的。 firewall-cm…

sqlite | c++ | demo

sqlite 过得的废话 就不细说了 接下来&#xff0c;主要讲 安装sqlite 然后写一个demo &#xff0c;然后再shell 命令操作sqlite #安装 sqlite 程序 以及开发包 我的linux 环境是centos sudo yum install sqlite-3.7.17-8.el7_7.1.x86_64 sqlite-devel#输入 sqlite3 测试是否安装…

[SAP] ABAP常用事务码以及系统变量

本问主要记录SAP ABAP开发模块比较常用的事务代码和系统变量 事务码(T-CODE) 事务码描述SE38ABAP编辑器SE11数据字典创建与维护SE12数据字典显示SE16N查看和编辑数据库表工具 系统变量 系统变量描述SY-UNAME用户名SY-DATUM当前日期SY-UZEIT当前时间SY-SUBRC执行状态为0表示…

K8S之configMapsecret

job 第一个是初始化尝试&#xff0c;初始化尝试失败之后&#xff0c;会再重试两次。 配置资源管理: Secret Configmap*:1.2加入的新特征 1.18 Secret: 保存密码&#xff0c;token,敏感的k8s资源 这类数据可以存放在镜像当中&#xff0c;但是防止secret当中可以更方便的控…

Java面试汇总——jvm篇

JVM的组成&#xff1a; 1、JVM 概述(⭐⭐⭐⭐) 1.1 JVM是什么&#xff1f; jvm(Java Virtual Machine)&#xff0c;是Java程序的运行环境&#xff08;java二进制字节码的运行环境&#xff09;。 优点&#xff1a; 一次编写&#xff0c;到处运行。(jvm屏蔽了各种操作系统)自动…