1. 程式人生 > >CODEFORCES:103E(二分圖匹配&最大流)

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

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

參考:

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 匈牙利演算法求二分圖的最大匹配

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
}