传送门 \looparrowright

题目描述

给定有向图 G=(V,E)G=(V,E) 。设 PPGG 的一个简单路(顶点不相交)的集合。如果 VV 中每个定点恰好在 PP 的一条路上,则称 PPGG 的一个路径覆盖。PP 中路径可以从 VV 的任何一个定点开始,长度也是任意的,特别地,可以为 00GG 的最小路径覆盖是 GG 所含路径条数最少的路径覆盖。设计一个有效算法求一个有向无环图 GG 的最小路径覆盖。

输入格式

第一行有 22 个正整数 nnmmnn 是给定有向无环图 GG 的顶点数,mmGG 的边数。接下来的 mm 行,每行有两个正整数 iijj 表示一条有向边 (i,j)(i,j)

输出格式

从第 11 行开始,每行输出一条路径。文件的最后一行是最少路径数。

输入输出样例

输入 #1

11 12
1 2
1 3
1 4
2 5
3 6
4 7
5 8
6 9
7 10
8 11
9 11
10 11

输出 #1

1 4 7 10 11
2 5 8
3 6 9
3

说明/提示

1n150,1m60001\leqslant n\leqslant 150,1\leqslant m\leqslant 6000

分析

设原来的有向无环图为 G=(V,E)G=(V,E)n=Vn=|V|。把 GG 中的每个点 xx 拆成编号为 xxx+nx+n 的两个点。建立一张新的二分图,1 n1 ~ n 作为二分图左部点,n+12nn+1\sim 2n 作为二分图右部点,对于原图的每条有向边 (x,y)(x,y),在二分图的左部点 xx 与右部点 y+ny+n 之间连边。最终得到的二分图称为 GG 的拆点二分图,记为 G2G_2
不加证明地给出定理:有向无环图 GG 的最小路径点覆盖包含的路径条数,等于 nn 减去拆点二分图 G2G_2 的最大匹配数;最小路径覆盖中的所有边,在拆点二分图 G2G_2 中构成一组匹配。最小路径覆盖中每条边 (x,y)(x,y) 的起点 xx 与二分图每条匹配边 (x,y+n)(x,y+n) 的左部点 xx 是一一对应的。
因此,对一个有向无环图,可建立拆点二分图,利用匈牙利算法求最大匹配,并且可以利用 match\mathrm{match} 数组输出匹配方案。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/******************************************************************
Copyright: 11D_Beyonder All Rights Reserved
Author: 11D_Beyonder
Problem ID: 洛谷 P2764
Date: 7/26/2020
Description: Hungarian Alogrithm
*******************************************************************/
#include<iostream>
#include<cstring>
using namespace std;
const int N=153;
const int M=6003;
struct E
{
int to;
int Next=-1;
}edge[M<<2];
int head[N<<2],tot;
int n,m;
bool vis[N<<2];
int match[N<<2];
void init();
inline void add_edge(int,int);
bool dfs(int);
int Hungarian();
int main()
{
cin>>n>>m;
init();
while(m--)
{
int x,y;
cin>>x>>y;
//建立拆点二分图
add_edge(x,y+n);
}
cout<<Hungarian()<<endl;
return 0;
}
void init()
{
memset(head,-1,sizeof(head));
tot=0;
memset(match,-1,sizeof(match));
}
inline void add_edge(int u,int v)
{
tot++;
edge[tot].to=v;
edge[tot].Next=head[u];
head[u]=tot;
}
bool dfs(int x)
{
for(register int i=head[x];~i;i=edge[i].Next)
{
int y=edge[i].to;
if(!vis[y])
{
//vis 记录右部节点的访问情况
vis[y]=1;
if(match[y]==-1||dfs(match[y]))
{
match[y]=x;
match[x]=y;
return 1;
}
}
}
return 0;
}
int Hungarian()
{
int ans=0;
int i;
for(i=1;i<=n;i++)
{
memset(vis,0,sizeof(vis));
ans+=dfs(i);
}
memset(vis,0,sizeof(vis));
for(i=1;i<=n;i++)
{
if(!vis[i])
{
//统一输出右部连向的左部点
int x=i+n;
do
{
x-=n;
vis[x]=1;
cout<<x<<' ';
x=match[x];
}while(~x);
cout<<endl;
}
}
return n-ans;//最小路径覆盖
}