zoukankan      html  css  js  c++  java
  • [题解] 丘比特的烦恼

    前言

    文化课选手,最近没多少时间写题解,这题做了快两周了。若题解有误,欢迎指出。

    简述费用流。

    题目链接

    题目大意

    在平面直角坐标系内,有 (n) 个男性, (n) 个女性。将这些男女配对,每对男女若配对成功,将做出贡献,这些贡献会在输入中描述,若没有描述,则贡献为 (1) 。配对是有条件的,若在平面直角坐标系内的,将这对男女用线段连起来,若中间没有别的人(不分男女),且两点之间的距离小于一个定值,则可以配对,反之不能。求做的最大贡献。(注意,没有基友,不能百合,不能开后宫!!!)

    易错点

    其实是输入问题。

    • 不区分大小写,先将名字全部转换为大写或小写。
    • 没有描述的人之间贡献为 (1)

    注意点就好了。

    思路

    男男女女的配对问题,很容易就想到是二分图带权匹配。其中,男性为左部点,女性为右部点,花费就为做的贡献。可以使用 (KM) 算法,但这里介绍使用费用流求解的二分图带权匹配。

    费用流如何来求解二分图的带权匹配很简单:将源点与左部点连接起来,将汇点用右部点连接起来,左部点与右部点该咋连就咋连。其中,每条边的容量为 (1) ,花费就为这条边的贡献。

    结合图像理解:
    在这里插入图片描述

    在本张路中,绿色的点是源点,粉色的点是汇点,红色的点是左部点,蓝色的点是右部点。

    先明确匹配的一条重要性质:每个点只有能有一条匹配边。也就意味着每个点只能被利用一次。而源点与汇点就很好地限制了每个点的利用,源点到达汇点只需要经过 (3) 条边,且严格遵守源点->左部点->右部点->汇点这条路径走。对于 (1) 条匹配边来说,它会消耗源点到达它的左部点,它的右部点到达汇点这条边,这就意味着它的左部点和右部点不能再次利用,从而满足匹配的这条性质。

    故而,按照上述方法建一张网络,对与这张网络跑一边最大费用最大流即可。本文使用著名又基础的 (Edmond-Karp) 算法实现。

    C++代码

    #include <map>
    #include <cmath>
    #include <queue>
    #include <string>
    #include <cstdio>
    #include <vector>
    #include <cstring>
    #include <iostream>
    using namespace std;
    #define eps 1e-100
    #define INF 0x3f3f3f3f
    #define Min(a, b) ((a) < (b) ? (a) : (b))
    #define Swap(a, b) ((a) ^= (b) ^= (a) ^= (b))
    void Quick_Read(int &N) {
    	N = 0;
    	int op = 1;
    	char c = getchar();
    	while(c < '0' || c > '9') {
    		if(c == '-')
    			op = -1;
    		c = getchar();
    	}
    	while(c >= '0' && c <= '9') {
    		N = (N << 1) + (N << 3) + (c ^ 48);
    		c = getchar();
    	}
    	N *= op;
    }
    struct Node {
    	int to, val, cost, rev;
    	Node() {}
    	Node(int T, int L, int C, int R) {
    		to = T;
    		val = L;
    		cost = C;
    		rev = R;
    	}
    };
    const int MAXN = 1e4 + 5;
    vector<Node> v[MAXN];
    map<string, int> name;
    int ship[MAXN][MAXN];
    int x[MAXN], y[MAXN];
    int k, n, s, t;
    deque<int> q;
    int dis[MAXN], maf[MAXN];
    pair<int, int> pre[MAXN];
    bool inque[MAXN];
    int ans;
    bool SPFA() {//寻找增广路
    	int iqn = 1, fis = 0;
    	for(int i = 0; i <= 2 * n + 1; i++)
    		inque[i] = false, dis[i] = -INF, maf[i] = INF;
    	dis[s] = 0;
    	inque[s] = true;
    	q.push_back(s);
    	while(!q.empty()) {
    		int now = q.front(); q.pop_front();
    		inque[now] = false;
    		fis -= dis[now];
    		iqn--;
    		int SIZ= v[now].size();
    		for(int i = 0; i < SIZ; i++) {
    			int next = v[now][i].to;
    			if(dis[next] < dis[now] + v[now][i].cost && v[now][i].val) {
    				dis[next] = dis[now] + v[now][i].cost;
    				maf[next] = Min(maf[now], v[now][i].val);
    				pre[next].first = now;
    				pre[next].second = i;
    				if(!inque[next]) {
    					inque[next] = true;
    					if(q.empty() || dis[next] < dis[q.front()] || dis[next] * iqn >= fis)
    						q.push_back(next);
    					else
    						q.push_front(next);
    					fis += dis[next] + v[now][i].cost;
    					iqn++;
    				}
    			}
    		}
    	}
    	return dis[t] != -INF;
    }
    int Update() {//沿着增广路增广
    	int now = t;
    	while(now != s) {
    		int next = pre[now].first;
    		int i = pre[now].second;
    		v[next][i].val -= maf[t];
    		v[now][v[next][i].rev].val += maf[t];
    		ans += v[next][i].cost * maf[t];
    		now = next;
    	}
    	return maf[t];
    }
    int Edmond_Karp() {//最大费用最大流
    	int res = 0;
    	while(SPFA())
    		res += Update();
    	return res;
    }
    double Dist_From_To(int A, int B) {//两点间距离公式
    	double frontx = (x[A] - x[B]) * (x[A] - x[B]) * 1.0;
    	double fronty = (y[A] - y[B]) * (y[A] - y[B]) * 1.0;
    	double dist = sqrt(frontx + fronty);
    	return dist;
    }
    bool Judge_Dist(int A, int B) {//中间是否有人,是否在射程之内
    	double dist = Dist_From_To(A, B);
    	double maxdist = k * 1.0;
    	if(dist > maxdist)
    		return false;
    	for(int i = 1; i <= 2 * n; i++) {
    		if(i == A || i == B)
    			continue;
    		if(Dist_From_To(A, i) + Dist_From_To(i, B) - dist <= eps)
    			return false;
    	}
    	return true;
    }
    void Convert_Big(string &str) {//名字转换为大写
    	int SIZ = str.length();
    	for(int i = 0; i < SIZ; i++)
    		if(str[i] >= 'a' && str[i] <= 'z')
    			str[i] += 'A' - 'a';
    }
    void Build() {//源点到左部点,右部点到汇点连边
    	t = 2 * n + 1;
    	for(int i = 1; i <= n; i++) {
    		int idi = v[i].size();
    		int ids = v[s].size();
    		v[s].push_back(Node(i, 1, 0, idi));
    		v[i].push_back(Node(s, 0, 0, ids));
    	}
    	for(int i = n + 1; i <= 2 * n; i++) {
    		int idi = v[i].size();
    		int idt = v[t].size();
    		v[i].push_back(Node(t, 1, 0, idt));
    		v[t].push_back(Node(i, 0, 0, idi));
    	}
    }
    void Read() {
    	int ValBtoG;
    	string girl, boy;
    	Quick_Read(k);
    	Quick_Read(n);
    	for(int i = 1; i <= 2 * n; i++) {
    		Quick_Read(x[i]);
    		Quick_Read(y[i]);
    		cin >> boy;
    		Convert_Big(boy);
    		name[boy] = i;
    	}
    	for(int i = 1; i <= n; i++)
    		for(int j = n + 1; j <= 2 * n; j++)
    			ship[i][j] = 1;
    	cin >> boy;
    	while(boy != "End") {
    		Convert_Big(boy);
    		cin >> girl;
    		Convert_Big(girl);
    		int B = name[boy], G = name[girl];
    		if(G < B)
    			Swap(B, G);
    		Quick_Read(ValBtoG);
    		ship[B][G] = ValBtoG;
    		cin >> boy;
    	}
    	for(int i = 1; i <= n; i++)
    		for(int j = n + 1; j <= 2 * n; j++)
    			if(Judge_Dist(i, j)) {
    				int idi = v[i].size();
    				int idj = v[j].size();
    				v[i].push_back(Node(j, 1, ship[i][j], idj));
    				v[j].push_back(Node(i, 0, -ship[i][j], idi));
    			}
    }
    int main() {
    	Read();
    	Build();
    	Edmond_Karp();
    	printf("%d", ans);
    	return 0;
    }
    
  • 相关阅读:
    单独编译和使用webrtc音频回声消除模块(附完整源码+测试音频文件)(转)
    Android手机直播(三)声音采集
    Android 音视频去回声、降噪(Android音频采集及回音消除)(转)
    Android中Selector的setSelected“方法不管用”
    Github的快捷键
    哔哩哔哩视频显示在Github的Markdown博客页方法
    小巧的屏幕录像软件oCam
    win7各种插件的下载与使用
    3D 打印机选择说明文档
    在Ubuntu 18.04中安装Pycharm及创建Pycharm快捷方式
  • 原文地址:https://www.cnblogs.com/C202202chenkelin/p/14167561.html
Copyright © 2011-2022 走看看