算法篇——最大流问题例题(非诚勿扰)

242 阅读2分钟

http://47.99.179.148/problem.php?id=1038

image-20201203113621980

import java.util.*;

/**
 * @author SJ
 * @date 2020/12/2
 */
public class MaxFlow {
    public static Scanner scanner = new Scanner(System.in);

    /**
     * 第一行输入T(T<=10)表示有T组数据。每组数据先输入两个正整数 n,m (n,m<=50),接下来n行每行先输入一个k,代表第i号男嘉宾中意的女嘉宾人数(k<=10),随后
     * 输入k个数用空格分开,代表这名男嘉宾中意的女嘉宾编号名单。
     */
    public static void main(String[] args) {
        int T = scanner.nextInt();
        for (int i = 0; i < T; i++) {
          //  System.out.println("男女嘉宾个数:");
            int n = scanner.nextInt(); //n个男嘉宾
            int m = scanner.nextInt();//m个女嘉宾

            //编号0 表示源点,G(i,j)!=0表示第i号男嘉宾中意第j号女嘉宾,第n+m+1 个编号表示汇点
            //源点与所有男生相连,汇点与所有女生相连,目标是找到源点到汇点的最大流
            //1到n表示男嘉宾,n到n+m表示女嘉宾

            //网络中的节点数量,男+女+源点+汇点
            int N = n + m + 2;
            int[][] G = new int[N][N];


            for (int j = 0; j < n; j++) {
           //     System.out.println("di"+(j+1)+"个:喜欢的人数");
                int k = scanner.nextInt();
                for (int p = 0; p < k; p++) {
         //           System.out.println("她们分别是");
                    int boyIndex = j + 1;

                    int girlIndex = scanner.nextInt()+n;
                    G[boyIndex][girlIndex] = 1;

                }

            }

            for (int j = 1; j < N - 1; j++) {
                if (j <= n)
                    G[0][j] = 1;//源点与男生相连
                else
                    G[j][N - 1] = 1;//汇点与女生相连

            }

            int maxFlow = maxFlow(G, N);
            System.out.println(maxFlow);

        }

    }


    //在网络中找到一条路径并更新网络
    public static Boolean findPath(int[][] G, int N) {


        int[] temp = new int[N];//存放找到的路径
        Arrays.fill(temp,-1);
        Set<Integer> visited = new HashSet<>();
        Queue<Integer> queue = new LinkedList<>();
        queue.add(0);//放入源点
        visited.add(0);
    s:    while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                Integer cur = queue.poll();
                if (cur == N - 1)//找到终点
                {
                    //修改残留网络;
                    for (int q = N - 1; q >0; ) {
                        int x = temp[q];
                        G[x][q] = 0;
                        G[q][x] = 1;
                        q = temp[q];

                    }

                    return true;
                }

                for (int j = 0; j < N; j++) {
                    //有边相连且没访问过
                    if (G[cur][j] != 0 && !visited.contains(j)) {
                        queue.offer(j);
                        visited.add(j);
                        temp[j] = cur;

                    }
                }
            }

        }

        return false;


    }
    public static int maxFlow(int[][] G,int N){
        int MaxFlow=0;
        Boolean flag=findPath(G,N);
        while (flag){
            flag=findPath(G,N);
            MaxFlow++;
        }
        return MaxFlow;
    }
}

image-20201203114003779

// MaxFlow.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <queue>
#include <set>
#include<list>

using namespace std;

#define maxn 102
int m, n;//m位女嘉宾,n位男嘉宾
int N ;
int a[maxn][maxn];//存放女嘉宾价格表
int G[maxn][maxn];//存放网络图
int Pre[maxn];//存放当前找到的从源点到汇点的路径
int cost[maxn];//存放源点到该点的最短路径
set<int> visited;//定义访问过的对象

//初始化
void initialize() {
	//初始化存放的路径,每次寻找路径时都要用
	for (int i = 0; i < N; i++) {
		Pre[i] = -1;
		if (i==0)
		{
			cost[i] = 0;
		}
		else
		{
			cost[i] = 999999;
		}
		
	}
	visited.clear();
}
int find(set<int> myset, int i) {
	int flag1 = -1;
	for (std::set<int>::iterator it = myset.begin(); it != myset.end(); ++it) {
		if (*it == i)
		{
			flag1 = 1;
		}
	}
	return flag1;

}
//更新残差网络
void updateG() {
	//更新残差网络和价格表
	for (int q = N - 1; q > 0; ) {
		int x = Pre[q];
		G[x][q] = 0;
		G[q][x] = 1;
		//a[q][x] = 0 - a[x][q];
		q = x;

	}

}

//找到一条代价最小的路径并更新G
int findPath() {
	initialize();
	int flag = -1;
	list<int> toBevisited;
	toBevisited.push_back(0);
	visited.insert(0);
	while (!toBevisited.empty())
	{

		int size = toBevisited.size();
		int cur = toBevisited.front();
		if (cur == N - 1)
		{
			//可以找到一条路径
			flag = 1;
		}
		toBevisited.pop_front();
		visited.erase(cur);
		for (int i = 0; i < N; i++) {
			if (G[cur][i] != 0)
			{
				//toBevisited.push_back(i);

				
				
					
					//如果当前的耗费较小,就更新
					if (cost[cur] + a[cur][i] < cost[i])
					{
						Pre[i] = cur;
						cost[i] = cost[cur] + a[cur][i];
							
						int flag1 = 0;
						//for (std::list<int>::iterator it = toBevisited.begin(); it != toBevisited.end(); ++it) {
							//if (*it == i)
							//{
								//flag1 = 1;
							//}
						//}
						if (visited.find(i)==visited.end())
						{
							toBevisited.push_back(i);
							visited.insert(i);
						}


					}
					


			}
		}
		

	}
	updateG();
	return flag;

}




int main()
{
	//第一行输入T(T<=10)表示有T组数据。每组数据先输入两个正整数 n,m (n,m<=50),紧接着输入m行每行n个数a[i][j],用空格隔开,代表i号女嘉宾如果和j号男嘉
	//宾牵手需要收取的费用。接下来输入n行每行先输入一个k,代表第i号男嘉宾中意的女嘉宾人数(k <= 10),随后输入k个数用空格分开,代表这名男嘉宾中意的女嘉宾
	//	编号名单。

	int T;
	cin >> T;
	while (T--) {
		cin >> n >> m;
		N = n + m + 2;
		for (int i = 0; i < maxn; ++i) {
			for (int j = 0; j < maxn; ++j) {
				G[i][j] = 0;
				a[i][j] = 0;
			}
		}
		for (int i = 1; i <= m; i++)
			for (int j = 1; j <= n; j++)
			{
				int t;

				cin >> t;
				a[j][i+n]=t;
				a[i + n][j] = -t;
			}

		//初始化G
		for (int i = 0; i < N; i++)
			for (int j = 0; j < N; j++)
				G[i][j] = 0;

		//根据初始条件构建G
		for (int i = 1; i <= n; i++) {
			int k;
			cin >> k;
			for (int j = 0; j < k; j++) {
				int boyIndex = i;
				int temp;
				cin >> temp;
				int girlIndex = temp + n;
				G[boyIndex][girlIndex] = 1;
			}
		}
		for (int j = 1; j < N-1; j++) {
			if (j <= n)
				G[0][j] = 1;//源点与男生相连
			else
				G[j][N - 1] = 1;//汇点与女生相连

		}
		int maxFlow = 0;
		int minCost = 0;
		int m = findPath();
		while (m!=-1)
		{
			maxFlow++;
			minCost += cost[N - 1];
			m = findPath();
			
		}
		cout << maxFlow << " " << minCost<<endl;




	}
	return 0;
}


/*
3
2 2
3 5
4 2
1 1
2 1 2
3 3
2 6 1
3 5 4
1 7 1
2 1 2
1 3
2 1 2
2 2
3 5
4 2
1 1
2 1 2
*/

1.建立网络(心动网络和费用表)

2.在网络中寻找一条从源点到汇点且花费最小的路径来更新网络(该条路径上的所有边反向)

3.直到图中再也找不到一条源点到汇点的路径。