P2764 最小路径覆盖问题

发布时间 2023-09-26 21:05:23作者: carp_oier

prologue

看见题解区好多神犇都是用 网络流 来做的,但是蒟蒻在刚学完 二部图 之后就来刷题了,对于这个题的路径输出有一个 比较新颖 的搞法,所以说就来写了这篇题解。

analysis

首先,我们为了将它转换成为一个 二部图,我们需要对它进行拆点操作(其实最后我跑起来并没有拆点),然后对它进行分析。

(下面左部为出度的点,右部为入度的点)

这个时候,原图中的每条路径转化到新图中,每一个点都可以对应出来一个匹配。每一条路径的 终点 都会对应到 左部 一个 非匹配点。这个时候我们要求 最小路径覆盖,就等价于 左部非匹配点最少,即这个图的 最大匹配。我们求最大匹配可以使用 匈牙利算法(之后输出合法路径需要)。

记我们的 最大匹配\(m\)最小路径覆盖 就为 \(n - m\)

之后我们再考虑怎么去输出合法路径。

我们观察我们上面匈牙利算法中的 \(match\) 数组,每一个 右部 点的 \(match\) 都会对应到我们一个新图中的一个 左部点,我们再将这个点放到 右部点 来看,会由它的 \(match\) 对应一个 左部点。重复以上过程,直到我们无法再匹配 左部点

这个不断往回匹配的过程我们可以用 递归 来解决,边界条件就是匹配到 \(0\)。我们只需要在跑这个递归的过程中用一个 \(vector\) 来记录路径就行。

code time

(我下面的这个 \(cnt\) 写的稀碎,我知道为啥,但是调不出来,除了占空间之外好像没啥缺点,还能当作一个(虚假的)可持久化来看)

using namespace std;
#define ll long long
#define rl register ll
#define x first
#define y second

typedef pair<ll, ll> pll;

const ll N = 160;

ll n, m;

bool st[N], g[N][N];

ll cnt, pre_cnt;

ll match[N];

pll pre[N];

vector<ll> path[N];

inline bool find(ll u)
{
	for(rl i=1; i <= n; ++ i) if(g[u][i] && !st[i])
	{
		st[i] = true;
		
		ll t = match[i];
		if(!t || find(t))
		{
			match[i] = u;
			return true;
		}
	}
	
	return false;
}

inline void get_path(ll u)
{
	path[cnt].push_back(u);
	st[u] = 1;
	if(!pre[u].y) { cnt ++ ; return ;}
	get_path(pre[u].y);
}

inline bool cmp(pll a, pll b)
{
	return a.y > b.y;
}

int main()
{
	cin >> n >> m;
	
	for(rl i=1; i <= m; ++ i)
	{
		ll a, b;
		cin >> a >> b;
		g[a][b] = true; 
	}
	
	ll res = 0;
		
	for(rl i=1; i <= n; ++ i)
	{
		memset(st, 0, sizeof st);
		if(find(i)) res ++ ;
	}
	
	memset(st, 0, sizeof st);
	
	for(rl i=1; i <= n; ++ i)
		pre[i] = {i, match[i]};
	
	for(rl i=n; i; -- i) if(!st[i] && pre[i].x) get_path(pre[i].x);
	
	for(rl i=cnt - n + res; i < cnt; ++ i)
	{
		for(rl j : path[i])
			cout << j << " ";
			
		cout << endl;
	}
	
	cout << n - res << endl;
	return 0;
}