[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;
}
相关文章
相关标签/搜索