CODEFORCES:103E(二分图匹配&最大流)

CODEFORCES 103E(二分图匹配&最大流)

参考:

最大权闭合子图(https://www.cnblogs.com/wuyiqi/archive/2012/03/12/2391960.html) 

CF 103E 解题报告(https://www.cnblogs.com/xuesu/p/4418868.html)

1. 题目

E. Buying Sets

The Hexadecimal virus loves playing withnumber sets — intersecting them, uniting them. One beautiful day she wassurprised to find out that Scuzzy, her spherical pet cat, united all sets inone and ate the result! Something had to be done quickly and Hexadecimal rushedto the market.

The market has n sets of numbers on sale. The virus wants to buy thefollowing collection of sets: the number of sets in the collection should beexactly the same as the number of numbers in the union of all bought sets.Moreover, Hexadecimal wants to buy the cheapest suitable collection of set.

Yet nothing's so easy! As Mainframe is akingdom of pure rivalry markets, we know that the union of any k sets contains no less than kdistinct numbers (for every positive integer k).

Help the virus choose the suitablecollection of sets. The collection can be empty.

Input

The first line contains the onlynumber n (1 ≤ n ≤ 300) — the number of sets available in the market.

Next n linesdescribe the goods: first we are given mi (1 ≤ mi ≤ n)— the number of distinct numbers in the i-thset, then follow mi numbers — the set's elements. We know that theset's elements are distinct positive integers and they do not exceed n.

The last line contains n integers whose absolute values do not exceed 106 —the price of each set.

Output

Print a single number — the minimum pricethe virus will have to pay for such a collection of k sets that union of the collection's sets would haveexactly k distinct numbers.

Examples

input

Copy

3
1 1
2 2 3
1 3
10 20 -3

output

Copy

-3

input

Copy

5
2 1 2
2 2 3
2 3 4
2 4 5
2 5 1
1 -1 1 -1 1

output

Copy

0

input

Copy

5
2 1 2
2 2 3
2 3 4
2 4 5
2 5 1
-1 1 -1 1 -1

output

Copy

-1

3. 中文大意

        给定n个集合,要求选出其中某些集合, 使得这些集合的并集的势,等于选出的集合的数目。对于任意的k(1<=k<=n), 满足从中选出任意k个集合, k个集合的并集的势一定大于等于k.每个集合有一个权值,每个选择方案的代价是所选的集合的权值的和。请输出代价最小的选择方案的代价。当然, 不选择任何一个集合是一个可行的方案(权值和为0),但不一定最优(权值和可以为负)


3. 解题思路

3.1 二分图匹配

       将n个集合看做二分图G1的左半部,n个元素看做二分图G1的右半部,由条件“从中选出任意k个集合,k个集合的并集的势一定大于等于k”,根据课本定理5.2.1知G1存在从左半部到右半部的完全匹配,又左半部与右半部的节点个数相等,故G1存在完美匹配。故而,对于每一个集合,都可以找到一个元素作为这个集合的编码,使得两两集合编码不同。选择某一个集合,为满足问题要求“选出其中某些集合, 使得这些集合的并集的势,等于选出的集合的数目”,需要同时选择以该集合所含元素为编号的所有集合。

3.2 最大权闭合子图

以集合为点构造图G2,节点为集合,节点的值为集合的价格,如果买下该集合的同时要买下别的一些集合,则从该集合出发向需要同时买下的集合连有向边表示集合之间的相互依赖关系。求买到总价最小的集合组, 就是求图G2的最小权闭合子图,将所有集合的价格取负, 将原问题“求最小价格的集合组,集合组的并的势与集合个数相等”转化为求图G的最大权闭合子图。

3.3 最小割与最大流

       在图G2的基础上,将原来有向边的权值为正无穷。添加源节点s, 从s向所有价格为正的节点连边,边的权值为节点对应的集合的价格;添加汇节点t,从所有价格为负的节点向t连边,边的权值为节点对应的集合的价格的绝对值,形成图G3.

定义2.3.1 简单割:割集的每条边都与源或汇关联。

定理2.3.1 图G3的简单割与图G2的闭合子图一一对应。

证明:

(1)闭合子图是简单割:

(反证法)如果闭合子图不是简单割,则有一条边两端点都不是源或汇,则闭合子图有一条指向子图外的节点的边,与“闭合子图”的闭合性矛盾。

(2)简单割是闭合子图:

简单割将图G3分成的两个集合中,源所在的集合除去源外,其余以其余各点为起始点的边都指向汇而没有指向其他节点的,因此这些节点构成闭合子图。

定理2.3.2 图G3的最小割将图G3分成的两个集合中,源所在的集合除去源的节点图G2的最大权闭合子图的节点。

证明:

记G2中全体正权值节点权值之和为TP。记一个简单割的容量为C,简单割把G3分成的两个子图中,源所在子图除源以外的节点构成集合S,汇所在的子图除汇以外的节点构成子图T。记T中所有正权值节点权值(即与源相连的边的权值)之和为x1,S中所有负权值节点权值绝对值(即与汇相连的边的权值)之和为y1。则由割集的定义 C = x1 + y1.

考虑在G2中由S构成的闭合子图B, B的权值记为W。B中正权值节点权值之和为x2, 负权值节点权值绝对值之和为y2,W = x2 – y2.

考虑C + W = x1 +y1 + x2 – y1. 显然由上述定义知 y1 =y2, x1 + x2 = TP. 故C + W = TP为常数。最大化W等价于最小化C,求图G2的最大权闭合子图等价于求图G2的最小割,图G3的最小割将图G3分成的两个集合中,源所在的集合除去源的节点图G2的最大权闭合子图的节点。

求最小割又等价于求最大流,故最终原问题等价于求图G3的最大流。

4. 关键代码分析

4.1 匈牙利算法求二分图的最大匹配

用匈牙利算法求2.1的最大匹配,其中,寻找增广道路的过程用递归的深度优先搜索实现。关于这部分内容可以参考我的另一篇博客:https://blog.csdn.net/da_kao_la/article/details/80299107

int dfs(int s, int n, vector<int>*G, int *link, bool *vset)	// 深搜求增广道路
{
	int i= 0, t = 0;
	for (i=0; i<G[s].size(); i++)
	{
		t = G[s].at(i);							// G[s]: s的后继节点集
		if (!vset[t-n])							// t没有被访问过
		{
			vset[t-n] = true;						// t被访问了
			if (link[t]==-1 || dfs(link[t],n,G,link,vset))	// 存在一条增广路径(递归深搜)
			{
				link[t] = s;
				link[s] = t;
				return 1;
			}
		}
	}
	return 0;
}

int match(int n, int m, vector<int>*G, int *link, bool *vset)
{
	int result = 0, i = 0, j = 0;
	for (i=0; i<n; i++)
	{
		for (j=0; j<m; j++)
		{
			vset[j] = false;
		}
		result += dfs(i,n,G,link,vset);			// 对左半部图的每一个节点求增广道路
	}
	return result;
}

4.2 Ford-Fulkerson算法求最大流

       用Ford-Fulkerson算法求2.3中的最大流。Ford-Fulkerson算法思想与匈牙利算法类似,每次寻找增流道路,直到找不到增流道路为止。

// 求最大流
int **flow = new int*[n+2];									// 道路上的流矩阵
for (i=0; i<n+2; i++)
{
	flow[i] = new int[n+2]();								// 流初始化为0
}
int *pre = new int[n+2];									// 标号过程的前驱节点
int *delta = new int[n+2]();								// 标号过程的增流量
bool *posorneg = new bool[n+2]();							// 是正向标记还是反向标记
for (i=0; i<n+2; i++)
{
	pre[i] = -2;											// -2表示未标记
}
int u = n;													// 当前正在标记的节点
bool hasUnmarked = false;									// 还有未标记节点
bool isend = false;											// 是否存在增流路径
deque<int> biaoji;											// 当前轮次已标记的节点集
int Delta = 0;												// 增流路径的增流
int w = 0;													// 最大流
while(1)
{
	biaoji.clear();											// 删除所有标记
	hasUnmarked = false;
	isend = true;
	for (i=0; i<n+2; i++)
	{
		pre[i] = -2;
		delta[i] = 0;
	}

	u = n;													// 首先标记源
	pre[n] = -1;											// 源无前驱节点
	delta[n] = INFI;										// 源的增流无穷大
	for (i=0; i<n+2; i++)
	{
		if (mat[n][i] != 0 && pre[i] == -2 && flow[n][i]<mat[n][i])					
			// 选取源的未标记的且可以正向标记的后继节点
		{
			hasUnmarked = true;
			pre[i] = n;
			posorneg[i] = true;
			delta[i] = mat[n][i] - flow[n][i];
			biaoji.push_back(i);
		}
		if (mat[i][n] != 0 && pre[i] == -2 && flow[i][n] > 0)
			// 选取源的未标记的且可以反向标记的后继节点
		{
			hasUnmarked = true;
			pre[i] = n;
			posorneg[i] = false;
			delta[i] = flow[i][n];
			biaoji.push_back(i);
		}
	}
	if (!hasUnmarked)										// 如果已经没有未标记的可标记节点
	{
		break;												// 已经是最大流分布,算法结束
	}
	while (!biaoji.empty())
	{
		u = biaoji.front();
		biaoji.pop_front();
		if (u == n+1)										// 汇已标记
		{
			isend = false;									// 有增流路径,算法仍未结束
			break;											// 已找到从源到汇的增流路径
		}
		for (i=0; i<n+2; i++)
		{
			if (mat[u][i] != 0 && pre[i] == -2 && flow[u][i]<mat[u][i])					
				// 选取未标记的且可以正向标记的后继节点
			{
				hasUnmarked = true;
				pre[i] = u;
				posorneg[i] = true;
				delta[i] = min(mat[u][i] - flow[u][i],delta[u]);
				biaoji.push_back(i);
			}
			if (mat[i][u] != 0 && pre[i] == -2 && flow[i][u] > 0)
				// 选取未标记的且可以反向标记的后继节点
			{
				hasUnmarked = true;
				pre[i] = u;
				posorneg[i] = false;
				delta[i] = min(flow[i][u],delta[u]);
				biaoji.push_back(i);
			}
		}
	}
	if (isend)
	{
		break;
	}
	else
	{
		// 增流过程
		u = n+1;			// 从汇出发回溯
		Delta = delta[n+1];
		while(u!=n)
		{
			if (posorneg[u])
			{
				flow[pre[u]][u] += Delta;
			}
			else
			{
				flow[u][pre[u]] -= Delta;
			}
			u = pre[u];
		}
		w += Delta;
	}
}
cout << -(TOT - w);			// 以上求得的是价格取负的最大权闭合子图,取反表示不取负的最小权闭合子图

5. 小结

本题要经过3次图论建模:(1)完美匹配二分图、(2)最大权闭合子图、(3)最小割网络流图,从(1)到(2)建模了条件“这些集合的并集的势, 等于选出的集合的数目”,从(2)到(3)使得最大权闭合子图问题可以转化为最大流问题用已有算法Ford-Fulkerson算法求解。


6. 完整代码

 
//D14728. Buying Sets
//时间限制:2.0s   内存限制:256.0MB  
//试题来源
//  CODEFORCES 103E
//问题描述
//  给定n个集合, 要求选出其中某些集合, 使得这些集合的并集的势, 等于选出的集合的数目.
//  对于任意的k(1<=k<=n), 满从中选出任意k个集合, 这k个集合的并集的势一定大于等于k.
//  每个集合有一个权值, 每个选择方案的代价是所选的集合的权值的和.
//  请输出代价最小的选择方案的代价.
//  当然, 不选择任何一个集合是一个可行的方案(权值和为0), 但不一定最优(权值和可以为负).
//输入格式
//  第一行一个正整数n(1<=n<=300), 为集合个数.
//  在接下来n行中, 第i行描述第i个集合:
//  首先给出一个正整数m[i]为该集合的势, 显然1<=m[i]<=n.
//  接下来m[i]个在1到n之间的整数, 表示该集合中的元素.
//  最后一行n个整数, 为每个集合的权值, 绝对值不超过1e6.
//输出格式
//  仅一个整数, 为代价最小的选择方案的代价.
//样例输入
//3
//1 1
//2 2 3
//1 3
//10 20 -3
//样例输出
//-3
//样例输入
//5
//2 1 2
//2 2 3
//2 3 4
//2 4 5
//2 5 1
//1 -1 1 -1 1
//样例输出
//0
//样例输入
//5
//2 1 2
//2 2 3
//2 3 4
//2 4 5
//2 5 1
//-1 1 -1 1 -1
//样例输出
//-1

// 1. 根据性质“从中选出任意k个集合, 这k个集合的并集的势一定大于等于k”,
//	  求集合到元素的一个完美匹配,使得每个集合可以用一个元素编码,两两集合的编码不同
// 2. 以集合为点构造图G,节点为集合,节点的值为集合的价格
//    如果买下该集合的同时要买下别的一些集合,则从该集合出发向需要同时买下的集合连有向边,
//    求买到总价最小的sets, 就是求图G的最小权闭合子图
//    将所有集合的价格取负, 转化为求图G的最大权闭合子图
// 3. 在图G的基础上将原来有向边的权值为正无穷
//    添加源节点s,从s向所有价格为正的节点连边,边的权值为节点对应的集合的价格
//    添加汇节点t,从所有价格为负的节点向t连边,边的权值为节点对应的集合的价格的绝对值,形成图G'
// 4. 可以证明,求图G的最大权闭合子图等价于求图G'的最小割
//    求最小割又等价于求最大流,故问题最终转化为求图G'的最大流

#include<fstream>
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;

const int INFI = (int)1e7;

int dfs(int s, int n, vector<int>*G, int *link, bool *vset)	// 深搜求增广道路
{
	int i= 0, t = 0;
	for (i=0; i<G[s].size(); i++)
	{
		t = G[s].at(i);							// G[s]: s的后继节点集
		if (!vset[t-n])							// t没有被访问过
		{
			vset[t-n] = true;						// t被访问了
			if (link[t]==-1 || dfs(link[t],n,G,link,vset))	// 存在一条增广路径(递归深搜)
			{
				link[t] = s;
				link[s] = t;
				return 1;
			}
		}
	}
	return 0;
}

int match(int n, int m, vector<int>*G, int *link, bool *vset)
{
	int result = 0, i = 0, j = 0;
	for (i=0; i<n; i++)
	{
		for (j=0; j<m; j++)
		{
			vset[j] = false;
		}
		result += dfs(i,n,G,link,vset);			// 对左半部图的每一个节点求增广道路
	}
	return result;
}


int main()
{
#ifndef ONLINE_JUDGE
	ifstream fin("data.txt");
	int n,i,capa,j,tmp;
	//vector< vector<int> > sets;			// 各个集合所含元素
	fin >> n;							// 集合的个数,也是元素的个数
	vector<int> *G = new vector<int>[2*n];			// 邻接表
	for (i=0; i<2*n; i++)
	{
		G[i].clear();
	}
	vector<int> price;					// 集合的价格的相反数
	vector<int> posPrice;				// 价格相反数为正的集合的元素编码
	vector<int> negPrice;				// 价格相反数为负的集合的元素编码
	for (i=0; i<n; i++)
	{
		fin >> capa;
		for (j=0; j<capa; j++)
		{
			fin >> tmp;
			tmp--;						// 将元素从1开始的编号改为从0开始
			G[i].push_back(tmp+n);
			G[n+tmp].push_back(i);
		}
	}
	int *link = new int[2*n];						// 匹配
	for (i=0; i<2*n; i++)
	{
		link[i] = -1;								// -1表示该节点尚未匹配
	}
	bool *vset = new bool[n];						// 在该轮迭代中该节点是否被访问过
	for (i=0; i<n; i++)
	{
		vset[i] = false;
	}
	// 匈牙利算法求最大匹配,此处即是完美匹配,匹配信息在link中
	int ans = match(n,n,G,link,vset);
	
	int setnum = 0;
	int TOT = 0;						// 所有取反以后为正的价格之和
	for (i=0; i<n; i++)
	{
		fin >> tmp;
		if (tmp >= 0)
		{
			negPrice.push_back(i);		// 价格相反数为负的集合
		}
		else
		{
			posPrice.push_back(i);		// 价格相反数为正的集合
			TOT += -tmp;
		}
		price.push_back(-tmp);			// 价格的相反数
	}
	fin.close();
	// 构造图G'
	int **mat = new int*[n+2];			// n个集合节点+源+汇
	for (i=0; i<n+2; i++)
	{
		mat[i] = new int[n+2]();		
	}
	for (i=0; i<n; i++)
	{
		vector<int> eleset = G[i];
		if (eleset.size()>1)
		{
			for (j=0; j<eleset.size(); j++)
			{
				if (i != link[eleset.at(j)])
				{
					mat[i][link[eleset.at(j)]] = INFI;	// 权值为无穷的有向边表示集合间的依赖关系
				}
			}
		}
	}
	for (i=0; i<posPrice.size(); i++)
	{
		mat[n][posPrice.at(i)] = price.at((posPrice.at(i)));	// 第n+1个节点是源节点
	}
	for (i=0; i<negPrice.size(); i++)
	{
		mat[negPrice.at(i)][n+1] = -price.at((negPrice.at(i)));	// 第n+2个节点是汇节点
	}
	delete[] G;
	// 求最大流
	int **flow = new int*[n+2];									// 道路上的流矩阵
	for (i=0; i<n+2; i++)
	{
		flow[i] = new int[n+2]();								// 流初始化为0
	}
	int *pre = new int[n+2];									// 标号过程的前驱节点
	int *delta = new int[n+2]();								// 标号过程的增流量
	bool *posorneg = new bool[n+2]();							// 是正向标记还是反向标记
	for (i=0; i<n+2; i++)
	{
		pre[i] = -2;											// -2表示未标记
	}
	int u = n;													// 当前正在标记的节点
	bool hasUnmarked = false;									// 还有未标记节点
	bool isend = false;											// 是否存在增流路径
	deque<int> biaoji;											// 当前轮次已标记的节点集
	int Delta = 0;												// 增流路径的增流
	int w = 0;													// 最大流
	while(1)
	{
		biaoji.clear();											// 删除所有标记
		hasUnmarked = false;
		isend = true;
		for (i=0; i<n+2; i++)
		{
			pre[i] = -2;
			delta[i] = 0;
		}

		u = n;													// 首先标记源
		pre[n] = -1;											// 源无前驱节点
		delta[n] = INFI;										// 源的增流无穷大
		for (i=0; i<n+2; i++)
		{
			if (mat[n][i] != 0 && pre[i] == -2 && flow[n][i]<mat[n][i])					
				// 选取源的未标记的且可以正向标记的后继节点
			{
				hasUnmarked = true;
				pre[i] = n;
				posorneg[i] = true;
				delta[i] = mat[n][i] - flow[n][i];
				biaoji.push_back(i);
			}
			if (mat[i][n] != 0 && pre[i] == -2 && flow[i][n] > 0)
				// 选取源的未标记的且可以反向标记的后继节点
			{
				hasUnmarked = true;
				pre[i] = n;
				posorneg[i] = false;
				delta[i] = flow[i][n];
				biaoji.push_back(i);
			}
		}
		if (!hasUnmarked)										// 如果已经没有未标记的可标记节点
		{
			break;												// 已经是最大流分布,算法结束
		}
		while (!biaoji.empty())
		{
			u = biaoji.front();
			biaoji.pop_front();
			if (u == n+1)										// 汇已标记
			{
				isend = false;									// 有增流路径,算法仍未结束
				break;											// 已找到从源到汇的增流路径
			}
			for (i=0; i<n+2; i++)
			{
				if (mat[u][i] != 0 && pre[i] == -2 && flow[u][i]<mat[u][i])					
					// 选取未标记的且可以正向标记的后继节点
				{
					hasUnmarked = true;
					pre[i] = u;
					posorneg[i] = true;
					delta[i] = min(mat[u][i] - flow[u][i],delta[u]);
					biaoji.push_back(i);
				}
				if (mat[i][u] != 0 && pre[i] == -2 && flow[i][u] > 0)
					// 选取未标记的且可以反向标记的后继节点
				{
					hasUnmarked = true;
					pre[i] = u;
					posorneg[i] = false;
					delta[i] = min(flow[i][u],delta[u]);
					biaoji.push_back(i);
				}
			}
		}
		if (isend)
		{
			break;
		}
		else
		{
			// 增流过程
			u = n+1;			// 从汇出发回溯
			Delta = delta[n+1];
			while(u!=n)
			{
				if (posorneg[u])
				{
					flow[pre[u]][u] += Delta;
				}
				else
				{
					flow[u][pre[u]] -= Delta;
				}
				u = pre[u];
			}
			w += Delta;
		}
	}
	cout << -(TOT - w);			// 以上求得的是价格取负的最大权闭合子图,取反表示不取负的最小权闭合子图

	delete[] link;
	delete[] vset;
	delete[] pre;
	delete[] delta;
	delete[] posorneg;
	for (i=0; i<n+2; i++)
	{
		delete[] mat[i];
	}
	delete[] mat;
#endif
#ifdef ONLINE_JUDGE
	int n,i,capa,j,tmp;
	//vector< vector<int> > sets;			// 各个集合所含元素
	cin >> n;							// 集合的个数,也是元素的个数
	vector<int> *G = new vector<int>[2*n];			// 邻接表
	for (i=0; i<2*n; i++)
	{
		G[i].clear();
	}
	vector<int> price;					// 集合的价格的相反数
	vector<int> posPrice;				// 价格相反数为正的集合的元素编码
	vector<int> negPrice;				// 价格相反数为负的集合的元素编码
	for (i=0; i<n; i++)
	{
		cin >> capa;
		for (j=0; j<capa; j++)
		{
			cin >> tmp;
			tmp--;						// 将元素从1开始的编号改为从0开始
			G[i].push_back(tmp+n);
			G[n+tmp].push_back(i);
		}
	}
	int *link = new int[2*n];						// 匹配
	for (i=0; i<2*n; i++)
	{
		link[i] = -1;								// -1表示该节点尚未匹配
	}
	bool *vset = new bool[n];						// 在该轮迭代中该节点是否被访问过
	for (i=0; i<n; i++)
	{
		vset[i] = false;
	}
	// 匈牙利算法求最大匹配,此处即是完美匹配,匹配信息在link中
	int ans = match(n,n,G,link,vset);
	
	int setnum = 0;
	int TOT = 0;						// 所有取反以后为正的价格之和
	for (i=0; i<n; i++)
	{
		cin >> tmp;
		if (tmp >= 0)
		{
			negPrice.push_back(i);		// 价格相反数为负的集合
		}
		else
		{
			posPrice.push_back(i);		// 价格相反数为正的集合
			TOT += -tmp;
		}
		price.push_back(-tmp);			// 价格的相反数
	}
	// 构造图G'
	int **mat = new int*[n+2];			// n个集合节点+源+汇
	for (i=0; i<n+2; i++)
	{
		mat[i] = new int[n+2]();		
	}
	for (i=0; i<n; i++)
	{
		vector<int> eleset = G[i];
		if (eleset.size()>1)
		{
			for (j=0; j<eleset.size(); j++)
			{
				if (i != link[eleset.at(j)])
				{
					mat[i][link[eleset.at(j)]] = INFI;	// 权值为无穷的有向边表示集合间的依赖关系
				}
			}
		}
	}
	for (i=0; i<posPrice.size(); i++)
	{
		mat[n][posPrice.at(i)] = price.at((posPrice.at(i)));	// 第n+1个节点是源节点
	}
	for (i=0; i<negPrice.size(); i++)
	{
		mat[negPrice.at(i)][n+1] = -price.at((negPrice.at(i)));	// 第n+2个节点是汇节点
	}
	delete[] G;
	// 求最大流
	int **flow = new int*[n+2];									// 道路上的流矩阵
	for (i=0; i<n+2; i++)
	{
		flow[i] = new int[n+2]();								// 流初始化为0
	}
	int *pre = new int[n+2];									// 标号过程的前驱节点
	int *delta = new int[n+2]();								// 标号过程的增流量
	bool *posorneg = new bool[n+2]();							// 是正向标记还是反向标记
	for (i=0; i<n+2; i++)
	{
		pre[i] = -2;											// -2表示未标记
	}
	int u = n;													// 当前正在标记的节点
	bool hasUnmarked = false;									// 还有未标记节点
	bool isend = false;											// 是否存在增流路径
	deque<int> biaoji;											// 当前轮次已标记的节点集
	int Delta = 0;												// 增流路径的增流
	int w = 0;													// 最大流
	while(1)
	{
		biaoji.clear();											// 删除所有标记
		hasUnmarked = false;
		isend = true;
		for (i=0; i<n+2; i++)
		{
			pre[i] = -2;
			delta[i] = 0;
		}

		u = n;													// 首先标记源
		pre[n] = -1;											// 源无前驱节点
		delta[n] = INFI;										// 源的增流无穷大
		for (i=0; i<n+2; i++)
		{
			if (mat[n][i] != 0 && pre[i] == -2 && flow[n][i]<mat[n][i])					
				// 选取源的未标记的且可以正向标记的后继节点
			{
				hasUnmarked = true;
				pre[i] = n;
				posorneg[i] = true;
				delta[i] = mat[n][i] - flow[n][i];
				biaoji.push_back(i);
			}
			if (mat[i][n] != 0 && pre[i] == -2 && flow[i][n] > 0)
				// 选取源的未标记的且可以反向标记的后继节点
			{
				hasUnmarked = true;
				pre[i] = n;
				posorneg[i] = false;
				delta[i] = flow[i][n];
				biaoji.push_back(i);
			}
		}
		if (!hasUnmarked)										// 如果已经没有未标记的可标记节点
		{
			break;												// 已经是最大流分布,算法结束
		}
		while (!biaoji.empty())
		{
			u = biaoji.front();
			biaoji.pop_front();
			if (u == n+1)										// 汇已标记
			{
				isend = false;									// 有增流路径,算法仍未结束
				break;											// 已找到从源到汇的增流路径
			}
			for (i=0; i<n+2; i++)
			{
				if (mat[u][i] != 0 && pre[i] == -2 && flow[u][i]<mat[u][i])					
					// 选取未标记的且可以正向标记的后继节点
				{
					hasUnmarked = true;
					pre[i] = u;
					posorneg[i] = true;
					delta[i] = min(mat[u][i] - flow[u][i],delta[u]);
					biaoji.push_back(i);
				}
				if (mat[i][u] != 0 && pre[i] == -2 && flow[i][u] > 0)
					// 选取未标记的且可以反向标记的后继节点
				{
					hasUnmarked = true;
					pre[i] = u;
					posorneg[i] = false;
					delta[i] = min(flow[i][u],delta[u]);
					biaoji.push_back(i);
				}
			}
		}
		if (isend)
		{
			break;
		}
		else
		{
			// 增流过程
			u = n+1;			// 从汇出发回溯
			Delta = delta[n+1];
			while(u!=n)
			{
				if (posorneg[u])
				{
					flow[pre[u]][u] += Delta;
				}
				else
				{
					flow[u][pre[u]] -= Delta;
				}
				u = pre[u];
			}
			w += Delta;
		}
	}
	cout << -(TOT - w);			// 以上求得的是价格取负的最大权闭合子图,取反表示不取负的最小权闭合子图

	delete[] link;
	delete[] vset;
	delete[] pre;
	delete[] delta;
	delete[] posorneg;
	for (i=0; i<n+2; i++)
	{
		delete[] mat[i];
	}
	delete[] mat;
#endif
}


猜你喜欢

转载自blog.csdn.net/da_kao_la/article/details/80300842
今日推荐