[BZOJ 1143] [CTSC2008] 祭祀river 【最長反鏈】

題目連接:BZOJ - 1143php

 

題目分析

這道題在BZOJ上只要求輸出可選的最多的祭祀地點個數,是一道求最長反鏈長度的裸題。ios

下面給出一些相關知識:閉包

在有向無環圖中,有以下的一些定義和性質:spa

鏈:一條鏈是一些點的集合,鏈上任意兩個點x, y,知足要麼 x 能到達 y ,要麼 y 能到達 x 。blog

反鏈:一條反鏈是一些點的集合,鏈上任意兩個點x, y,知足 x 不能到達 y,且 y 也不能到達 x。get

那麼很顯然這道題就是求最長反鏈長度了。string

一個定理:最長反鏈長度 = 最小鏈覆蓋(用最少的鏈覆蓋全部頂點)it

對偶定理:最長鏈長度 = 最小反鏈覆蓋                                                                  io

那麼咱們要求出的就是這個有向無環圖的最小鏈覆蓋了。最小鏈覆蓋也就是路徑能夠相交的最小路徑覆蓋。class

咱們先來看路徑不能相交的最小路徑覆蓋怎麼來作:

創建一個二分圖,兩邊都是n個點,原圖的每一個點 i 對應兩個,在左邊的叫作 i1, 在右邊的叫作 i2 。

 

而後原圖中若是存在一條邊 (x, y),那麼就在二分圖中創建 (x1, y2) 的邊。

這樣創建二分圖以後,原圖的點數 n - 二分圖最大匹配 = 原圖的最小路徑覆蓋(路徑不能相交)。

這樣爲何是對的呢?咱們能夠認爲,開始時原圖的每一個點都是獨立的一條路徑,而後咱們每次在二分圖中選出一條邊,就是將兩條路徑鏈接成一條路徑,答案數就減小1。

而路徑是不能相交的,因此咱們在二分圖中選出的邊也是不能相交的,因此就是二分圖的最大匹配。

瞭解了路徑不能相交的最小路徑覆蓋以後,怎麼解路徑能夠相交的最小路徑覆蓋(也就是最小鏈覆蓋)呢?

咱們將原圖作一次Floyd傳遞閉包,以後就能夠知道任意兩點 x, y,x 是否能到達 y。

若是兩個點 x, y,知足 x 能夠到達 y ,那麼就在二分圖中創建邊 (x1, y2) 。

這樣其實就是至關於將原圖改造了一下,只要 x 能到達 y ,就直接連一條邊 (x, y),這樣就能夠「繞過」原圖的一些被其餘路徑佔用的點,直接構造新路徑了。

這樣就將能夠相交的最小路徑覆蓋轉化爲了路徑不能相交的最小路徑覆蓋了。

 

另外有一個最長反鏈=最小鏈覆蓋的例子,NOIP1999 導彈攔截,第二問實質上就是求最小鏈覆蓋,轉化爲最長反鏈來求,固然當時我寫那道題的時候就是看題解,根本不知道這是求最長反鏈= =

 

代碼

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int MaxN = 100 + 5;

int n, m, Ans, Index;
int Used[MaxN * 2], Father[MaxN * 2];

bool OK[MaxN][MaxN];

struct Edge
{
	int v;
	Edge *Next;
} E[MaxN * MaxN], *P = E, *Point[MaxN];

inline void AddEdge(int x, int y) 
{
	++P; P -> v = y;
	P -> Next = Point[x]; Point[x] = P;
}

bool Find(int x) 
{
	for (Edge *j = Point[x]; j; j = j -> Next) 
	{
		if (Used[j -> v] == Index) continue;
		Used[j -> v] = Index;
		if (Father[j -> v] == 0 || Find(Father[j -> v]))
		{
			Father[j -> v] = x;
			return true;
		}
	}
	return false;
}

int main()
{
	scanf("%d%d", &n, &m);
	int a, b;
	for (int i = 1; i <= m; ++i) 
	{
		scanf("%d%d", &a, &b);
		OK[a][b] = true;
	}
	for (int k = 1; k <= n; ++k) 
		for (int i = 1; i <= n; ++i)
			for (int j = 1; j <= n; ++j)
				OK[i][j] = OK[i][j] || (OK[i][k] && OK[k][j]);
	for (int i = 1; i <= n; ++i) 
		for (int j = 1; j <= n; ++j)
			if (OK[i][j]) AddEdge(i, n + j);
	Index = 0;
	Ans = 0;
	for (int i = 1; i <= n; ++i)
	{
		++Index;
		if (Find(i)) ++Ans;
	}
	Ans = n - Ans;
	printf("%d\n", Ans);
	return 0;
}
相關文章
相關標籤/搜索