洛谷 P4298: bzoj 1143: [CTSC2008]祭祀


題目傳送門:洛谷 P4298

題意簡述:

給定一個 \(n\) 個點,\(m\) 條邊的簡單有向無環圖(DAG),求出它的最長反鏈,並構造方案。

最長反鏈:一張有向無環圖的最長反鏈為一個集合 \(S \subseteq V\),滿足對於 \(S\) 中的任意兩個不同的點 \(u, v \in S\)\(u \ne v\)),\(u\) 不能到達 \(v\)\(v\) 也不能到達 \(u\),且 \(S\) 的大小盡量大。

題解:

根據 Dilworth 定理,一個 DAG 中最長反鏈的大小,等於其中最小可重鏈覆蓋大小。

最小可重鏈覆蓋:在 DAG 中選出若干條鏈,經過每個點至少一次,一個點可被一條鏈經過多次,且鏈數盡量少。

其實 Dilworth 定理描述的是:一個偏序集中的最長反鏈大小,等於其中最小不可重鏈覆蓋大小。
但是因為求出 DAG 的傳遞閉包后,DAG 也可以表示成偏序集的形式,所以相當於可重鏈覆蓋。

總之先求出 DAG 的傳遞閉包,然后求形成的偏序集上的最小不可重鏈覆蓋。

那么,最小不可重鏈覆蓋應該怎么求呢?

考慮從每個點自成一條鏈的形態出發,此時恰好有 \(n\) 條鏈。
可以發現最終答案一定是合並(首尾相接)若干條鏈形成的。考慮重新描述這個過程:
對於一個點,它在最終的鏈上,一定只有最多一個前驅,和最多一個后繼。
我們考慮把每個點拆成入點和出點,那么入點和出點應該只能匹配上最多一個點(表示前驅或者后繼)。

這似乎是二分圖匹配的形式,具體地,我們考慮:
把一個點 \(x\) 拆成兩個點:\(x_{out}\)\(x_{in}\),表示出點和入點。
對於一條邊 \(x \to y\),連接 \(x_{out}\)\(y_{in}\),表示原圖中 \(x\) 的出邊指向 \(y\)(這條邊是 \(y\) 的入邊)。
那么最終形成了一個二分圖,左側是所有 \(x_{out}\),右側是所有 \(x_{in}\)。而且所有邊都是連接左側的點和右側的點的。

在這個二分圖 \(G = \langle \langle V_{out}, V_{in} \rangle , E' \rangle\) 上做二分圖最大匹配:
每一個匹配邊 \(x_{out} \leftrightarrow y_{in}\) 都可以還原原圖中鏈的一條邊 \(x \to y\)
每匹配 \(1\) 條邊,鏈的個數就減少 \(1\),則有最小鏈覆蓋的大小等於 \(n\) 減去最大匹配的大小。

繼續考慮如何從二分圖最大匹配中,構造出最長反鏈。以下部分參考了 r_64 的題解

我們首先需要構造二分圖最大獨立集,這部分參考了「圖的最大匹配算法」這篇博客:

考慮下圖,可以求出它的其中一種最大匹配為 \(\{ \langle 2, D \rangle, \langle 3, E \rangle, \langle 4, A \rangle, \langle 5, C \rangle \}\),設最大匹配大小為 \(m\),這里 \(m = 4\)

從右側的非匹配點(這里為 \(B\),可能有多個)開始 DFS,右側的點只能走非匹配邊向左訪問,左側的點只能走匹配邊向右訪問:

可以發現 DFS 到了 \(3, 5, B, C, E\) 這些點。

我們取左側被 DFS 到的點,以及右側沒被 DFS 到的點,也就是 \(3, 5, A, D\) 這些點,記做集合 \(S\),可以證明 \(S\) 是一個最小點覆蓋。
最小點覆蓋:選取最少的點,覆蓋每條邊,也就是說每條邊的兩個端點至少有一個被選中了。

證明:

  1. 首先有:最小點覆蓋等於最大匹配。我們可以證明 \(|S| = m\)
    這是因為:右側的非匹配點一定都被 DFS 到了,所以在右側選取的必然是匹配點。如果一個右側的匹配點沒被選取,即它被 DFS 到了,而這只有可能是因為它在左側匹配到的點被 DFS 到了,那么左側匹配到的點就會被選上。即是:每條匹配邊的兩端點恰好會被選一個。而左側的非匹配點一定不會被 DFS 到,這是因為如果被 DFS 到了,必然會形成一條交錯路(匈牙利算法中的),不滿足最大匹配的條件。所以有且僅有匹配邊的端點會被選上,而且每條匹配邊的兩端點恰好被選一個,所以 \(\boldsymbol{|S| = m}\)

  2. \(S\) 可以覆蓋所有的邊。
    我們把邊按照左右端點是否被 DFS 到,分成 \(2 \times 2 = 4\) 類。那么如果出現了左端點沒被 DFS 到,但是右端點被 DFS 到了的邊,它才不會被覆蓋。然而這是不可能的,這是因為對於一個右側被 DFS 到的點,與它相連的左側的點一定都被 DFS 到了。

然后有最大獨立集等於最小點覆蓋的補集。也就是只要選出左側沒被 DFS 到的點和右側被 DFS 到的點就行了。
在上圖中就是 \(1, 2, 4, B, C, E\)\(6\) 個點。

回到 DAG 的情況(注意到我們舉的例子並不是 DAG 導出的二分圖,所以這個例子不能用來解釋最長反鏈):

令最大獨立集為 \(I\),考慮選出所有 \(x_{out}\)\(x_{in}\) 都屬於 \(I\) 的點,記做集合 \(A\),它們構成一個最長反鏈。

證明:
先證 \(A\) 的確是一個反鏈:這是容易的,因為任取 \(x \in A\)\(x_{in}\) 就一定是被 DFS 到的點,而 \(x_{out}\) 一定是沒被 DFS 到的點,任何兩個 \(x, y \in A\) 之間若是有連邊就和 DFS 的過程沖突了。
首先有 \(|I| = 2n - |S| = 2n - m\),而 \(|I| - |A|\) 可以看作是滿足「\(x_{out}\)\(x_{in}\) 屬於 \(I\)」的 \(x\) 的個數,顯然這樣的 \(x\) 不會超過 \(n\) 個,所以 \(|I| - |A| \le n\),所以 \(|A| \ge |I| - n = n - m\)
但是 \(A\) 再大,也不能大過 \(n - m\),所以 \(|A| = n - m\),也就是一個最長反鏈。

總結:只要選出 \(x_{out}\) 沒被 DFS 到,且 \(x_{in}\) 被 DFS 到了的點,這些點就組成一個最長反鏈。

然后是第三問,這只要默認該點被選中,也就是刪除這個點和與其有偏序關系的所有點后,再求一次最長反鏈,如果最長反鏈的大小只減小了 \(1\),那么這個點就能在最長反鏈中,否則不能。

下面是代碼,復雜度為 \(\mathcal O (n^{3.5})\)

#include <cstdio>
#include <algorithm>
#include <bitset>

namespace Dinic {
	const int Inf = 0x3f3f3f3f;
	const int MN = 205, MM = 5155;
	
	int N, S, T;
	int h[MN], iter[MN], nxt[MM * 2], to[MM * 2], w[MM * 2], tot;
	
	inline void Init(int _N) {
		N = _N, tot = 1;
		for (int i = 1; i <= N; ++i) h[i] = 0;
	}
	inline void SetST(int _S, int _T) { S = _S, T = _T; }
	
	inline void ins(int u, int v, int x) { nxt[++tot] = h[u], to[tot] = v, w[tot] = x, h[u] = tot; }
	inline void insw(int u, int v, int w1 = Inf, int w2 = 0) {
		if (!u) u = S; if (!v) v = T;
		ins(u, v, w1), ins(v, u, w2);
	}
	
	int lv[MN], que[MN], l, r;
	
	inline bool Lvl() {
		for (int i = 1; i <= N; ++i) lv[i] = 0;
		lv[S] = 1;
		que[l = r = 1] = S;
		while (l <= r) {
			int u = que[l++];
			for (int i = h[u]; i; i = nxt[i])
				if (w[i] && !lv[to[i]]) {
					lv[to[i]] = lv[u] + 1;
					que[++r] = to[i];
				}
		}
		return lv[T] != 0;
	}
	
	int Flow(int u, int f) {
		if (u == T) return f;
		int d = 0, s = 0;
		for (int &i = iter[u]; i; i = nxt[i])
			if (w[i] && lv[to[i]] == lv[u] + 1) {
				d = Flow(to[i], std::min(f, w[i]));
				f -= d, s += d;
				w[i] -= d, w[i ^ 1] += d;
				if (!f) break;
			}
		return s;
	}
	
	inline int DoDinic() {
		int Ans = 0;
		while (Lvl()) {
			for (int i = 1; i <= N; ++i) iter[i] = h[i];
			Ans += Flow(S, Inf);
		}
		return Ans;
	}
}
using Dinic::Init;
using Dinic::SetST;
using Dinic::insw;
using Dinic::DoDinic;
using Dinic::h;
using Dinic::nxt;
using Dinic::to;
using Dinic::w;

const int MN = 105;

int N, M, Ans;
std::bitset<101> g[MN];

int match[MN], tagl[MN], tagr[MN];
void DFS(int u) {
	tagr[u] = 1;
	for (int i = 1; i <= N; ++i)
		if (g[i][u] && !tagl[i])
			tagl[i] = 1, DFS(match[i]);
}

int main() {
	scanf("%d%d", &N, &M);
	for (int i = 1; i <= M; ++i) {
		int x, y;
		scanf("%d%d", &x, &y);
		g[x][y] = 1;
	}
	for (int k = 1; k <= N; ++k)
		for (int i = 1; i <= N; ++i)
			if (g[i][k]) g[i] |= g[k];
	Init(N + N + 2), SetST(N + N + 1, N + N + 2);
	for (int i = 1; i <= N; ++i)
		insw(0, i, 1), insw(N + i, 0, 1);
	for (int i = 1; i <= N; ++i)
		for (int j = 1; j <= N; ++j)
			if (g[i][j]) insw(i, N + j, 1);
	Ans = N - DoDinic();
	printf("%d\n", Ans);
	for (int i = 1; i <= N; ++i) if (!w[4 * i - 2]) {
		for (int j = h[i]; j; j = nxt[j])
			if (!w[j]) { match[i] = to[j] - N; break; }
	}
	for (int i = 1; i <= N; ++i) if (w[4 * i]) DFS(i);
	for (int i = 1; i <= N; ++i) printf("%d", !tagl[i] && tagr[i]);
	puts("");
	for (int u = 1; u <= N; ++u) {
		static int del[MN]; int cnt = 0;
		for (int i = 1; i <= N; ++i) del[i] = i == u || g[i][u] || g[u][i];
		Init(N + N + 2), SetST(N + N + 1, N + N + 2);
		for (int i = 1; i <= N; ++i) if (!del[i])
			insw(0, i, 1), insw(N + i, 0, 1), ++cnt;
		for (int i = 1; i <= N; ++i) if (!del[i])
			for (int j = 1; j <= N; ++j) if (!del[j])
				if (g[i][j]) insw(i, N + j, 1);
		printf("%d", cnt - DoDinic() == Ans - 1);
	} puts("");
	return 0;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM