[CTSC2008]祭祀(二分图+网络流)

Posted clfzs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[CTSC2008]祭祀(二分图+网络流)相关的知识,希望对你有一定的参考价值。

题目:洛谷P4298

题目描述:

给定一个\\(n\\)个点,\\(m\\)条边的有向无环图(\\(DAG\\)),求最多能选多少个点使得它们两两之间不能到达,并且要求你构造出一种方案,且判断每个点是否有可能被选出

\\(n \\leq 100\\)\\(m \\leq 1000\\)

蒟蒻题解:

在一个\\(DAG\\)中,它的最长反链就是选出一个点集,使得它们两两之间不能到达,并且使这个点集最大

题目中有三个问题

问题一

最多能选多少个点使得它们两两之间不能到达,即求这个\\(DAG\\)的最长反链为多大

根据\\(Dilworth\\)定理,一个\\(DAG\\)的最长反链的大小和最小可重链覆盖大小相等。

问题转换成求这个\\(DAG\\)的最小可重链覆盖的大小

\\(DAG\\)最小可重链覆盖的大小,可以先以\\(\\mathcal O(\\frac{n^3}{64})\\)的复杂度求出它的传递闭包,再转换成求二分图的最小不可重链覆盖

具体地,我们建一个二分图,左边有\\(n\\)个节点,右边也有\\(n\\)个节点,不妨设左边第\\(i\\)个节点为\\(a_i\\),右边第\\(i\\)个节点为\\(b_i\\)

对于\\(DAG\\)中,\\(u\\)能到达\\(v\\),那么我们在\\(a_u\\)\\(b_v\\)之间连一条边

根据二分图上,最小不可重链覆盖大小等于点数减去最大匹配大小,可以网络流求二分图的最大匹配,由于边数是\\(n^2\\)级别的,所以这部分的复杂度是\\(\\mathcal O(n^2\\sqrt{n})\\)

问题二

构造一组方案

考虑二分图上的最大独立集和\\(DAG\\)中的最长反链之间的关系

令二分图上最大匹配的大小为\\(X\\),则最大独立集的大小就为\\(2n-X\\),对于\\(a_i\\)\\(b_i\\)均在最大独立集的点\\(i\\),它们全部取出来可以构成\\(DAG\\)的一条反链

假设对于一个最大独立集,我们将\\(a_i\\)\\(b_i\\)均在最大独立集的点\\(i\\)全部取出来,记个数为\\(t\\),由于最长反链的大小为\\(n-X\\),所以\\(t \\leq n-X\\)

则剩下最大独立集中的点数为\\(2n-X-t \\geq 2n-X-(n-X)=n\\),又独立的点最多就\\(n\\)个,所以\\(2n-X-t \\leq n\\),故\\(2n-X-t=n\\),所以\\(t = n - X\\)

所以我们只需要任意找到一组二分图上的最大独立集,就能找出\\(DAG\\)的一个最长反链

现在问题转换为求出一组二分图的最大独立集

又由于二分图上最大独立集与最小点覆盖互补,求出一组最小点覆盖即可

找出一组最小点覆盖,在找到一组最大匹配的基础上,我们可以从右边的非匹配点开始\\(dfs\\),每次从右边走非匹配边到左边,然后从左边走匹配边到右边,然后取左边被\\(dfs\\)到的点和右边没有被\\(dfs\\)到的点可以组成一组最小点覆盖

证明:

  • 对于匹配点集中的点,一条匹配边如果左端点被\\(dfs\\)过,那么左边走匹配边到右边,右端点也会被\\(dfs\\)过,所以只取了左端点;一条匹配边如果左端点没被\\(dfs\\)过,由于起点是右边的非匹配点,且往右走每次都是走匹配边,所以这条匹配边的右端点也没有被\\(dfs\\)过,所以只取了右端点
  • 对于非匹配点集中的点,由于从所以右边的非匹配点开始\\(dfs\\),所以右边的非匹配点都被\\(dfs\\)过,对于左边的非匹配点,如果它被\\(dfs\\)过,由于每次都是走非匹配边-匹配边-非匹配边-匹配边-···-非匹配边,这条路径上非匹配边的个数比匹配边的个数多\\(1\\),让这条路径上的非匹配边和匹配边互换,最大匹配多\\(1\\),不满足原先匹配为最大匹配
  • 综上,可以得出这样取能取到一组最小点覆盖

最大独立集与最小点覆盖互补,即取左边没有被\\(dfs\\)到的点和右边被\\(dfs\\)到的点

所以最长反链就是取所有\\(a_i\\)没有被\\(dfs\\)\\(b_i\\)\\(dfs\\)到的点\\(i\\)

由于每个点只会被遍历一次,所以这部分的时间复杂度是\\(\\mathcal O(n)\\)

问题三

判断每个点是否可能存在于最长反链中

对于每个点,假设它存在于最长反链中,那么在这个\\(DAG\\)中,能到达它的点和它能到达的点都不会存在于这个最长反链中,将这些点全部删去,判断剩下的点构成的最长反链长度是否为原先得到的答案减\\(1\\)

时间复杂度为\\(\\mathcal O(n^3 \\sqrt{n})\\)

总的时间复杂度为\\(\\mathcal O(n^3 \\sqrt{n})\\)

参考程序:

#include<bits/stdc++.h>
using namespace std;
#define Re register int

const int N = 205, M = 20405;
int n, m, t, cnt = 1, ans, l, r, hea[N], h[N], nxt[M], to[M], d[N], q[N];
bool wi[M], p[N], pp[N], b[N];
bitset<N> bt[N];

inline int read()
{
	char c = getchar();
	int ans = 0;
	while (c < 48 || c > 57) c = getchar();
	while (c >= 48 && c <= 57) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
	return ans;
}

inline void add(int x, int y)
{
	nxt[++cnt] = hea[x], to[cnt] = y, wi[cnt] = 1, hea[x] = cnt;
	nxt[++cnt] = hea[y], to[cnt] = x, hea[y] = cnt;
}

inline bool bfs()
{
	for (Re i = 0; i <= t; ++i) h[i] = hea[i], d[i] = 0;
	d[l = r = 0] = 1;
	while (l <= r)
	{
		int x = q[l++];
		for (Re i = hea[x]; i; i = nxt[i])
		{
			int u = to[i];
			if (b[u] || !wi[i] || d[u]) continue;
			d[q[++r] = u] = d[x] + 1;
			if (u == t) return 1;
		}
	}
	return 0;
}

inline int dinic(int x, int y)
{
	if (x == t) return y;
	int res = y;
	for (Re &i = h[x]; i; i = nxt[i])
	{
		int u = to[i];
		if (!wi[i] || d[u] ^ d[x] + 1) continue;
		bool v = dinic(u, min(res, 1));
		if (v)
		{
			wi[i] = 0, wi[i ^ 1] = 1, --res;
			if (!res) return y;
		}
	}
	return y - res;
}

inline void dfs(int x)
{	
	p[x] = 1;
	for (Re i = hea[x]; i; i = nxt[i])
		if (to[i] && to[i] < t && !wi[i] && !p[to[i]]) dfs(to[i]);
}

int main()
{
	n = ans = read(), m = read(), t = n << 1 | 1;
	for (Re i = 1; i <= n; ++i) add(0, i), add(n + i, t);
	for (Re i = 0; i < m; ++i)
	{
		int u = read(), v = read();
		bt[u][v] = 1;
	}
	for (Re i = 1; i <= n; ++i)
		for (Re j = 1; j <= n; ++j)
			if (bt[j][i]) bt[j] |= bt[i];
	for (Re i = 1; i <= n; ++i)
		for (Re j = 1; j <= n; ++j)
			if (bt[i][j]) add(i, n + j);
	int fl;
	while (bfs())
		while (fl = dinic(0, n)) ans -= fl;
	printf("%d\\n", ans);
	for (Re i = hea[t]; i; i = nxt[i])
		if (!wi[i] && !p[to[i]]) dfs(to[i]);
	for (Re i = 1; i <= n; ++i)
		if (!p[i] && p[n + i]) pp[i] = 1;
	for (Re i = 1; i <= n; ++i) putchar(48 + pp[i]);
	putchar(\'\\n\');
	for (Re i = 1; i <= n; ++i)
		if (!pp[i])
		{
			int s = n - 1;
			b[i] = b[n + i] = 1;
			for (Re j = 1; j <= n; ++j)
				if (bt[i][j] | bt[j][i]) b[j] = b[n + j] = 1, --s;
			for (Re j = 2; j < cnt; j += 2) wi[j] = 1, wi[j ^ 1] = 0;
			while (bfs())
				while (fl = dinic(0, n)) s -= fl;
			pp[i] = (s == ans - 1), b[i] = b[n + i] = 0;
			for (Re j = 1; j <= n; ++j)
				if (bt[i][j] | bt[j][i]) b[j] = b[n + j] = 0;
		}
	for (Re i = 1; i <= n; ++i) putchar(48 + pp[i]);
	return 0;
}

以上是关于[CTSC2008]祭祀(二分图+网络流)的主要内容,如果未能解决你的问题,请参考以下文章

BZOJ 1143: [CTSC2008]祭祀river 最大独立集

Bzoj 2718: [Violet 4]毕业旅行 && Bzoj 1143: [CTSC2008]祭祀river 传递闭包,二分图匹配,匈牙利,bitset

bzoj1143: [CTSC2008]祭祀river

[BZOJ]1143: [CTSC2008]祭祀river

[CTSC 2008] 祭祀

题解 P4298 [CTSC2008]祭祀