[題解] 丘位元的煩惱(費用流求二分圖帶權匹配)
阿新 • • 發佈:2020-12-21
前言
文化課選手,最近沒多少時間寫題解,這題做了快兩週了。若題解有誤,歡迎指出。
題目大意
在平面直角座標系內,有 \(n\) 個男性, \(n\) 個女性。將這些男女配對,每對男女若配對成功,將做出貢獻,這些貢獻會在輸入中描述,若沒有描述,則貢獻為 \(1\) 。配對是有條件的,若在平面直角座標系內的,將這對男女用線段連起來,若中間沒有別的人(不分男女),且兩點之間的距離小於一個定值,則可以配對,反之不能。求做的最大貢獻。(注意,沒有基友,不能百合,不能開後宮!!!)
易錯點
其實是輸入問題。
- 不區分大小寫,先將名字全部轉換為大寫或小寫。
- 沒有描述的人之間貢獻為 \(1\)
注意點就好了。
思路
男男女女的配對問題,很容易就想到是二分圖帶權匹配。其中,男性為左部點,女性為右部點,花費就為做的貢獻。可以使用 \(KM\) 演算法,但這裡介紹使用費用流求解的二分圖帶權匹配。
費用流如何來求解二分圖的帶權匹配很簡單:將源點與左部點連線起來,將匯點用右部點連線起來,左部點與右部點該咋連就咋連。其中,每條邊的容量為 \(1\) ,花費就為這條邊的貢獻。
結合影象理解:
在本張路中,綠色的點是源點,粉色的點是匯點,紅色的點是左部點,藍色的點是右部點。
先明確匹配的一條重要性質:每個點只有能有一條匹配邊。也就意味著每個點只能被利用一次。而源點與匯點就很好地限制了每個點的利用,源點到達匯點只需要經過 \(3\)
故而,按照上述方法建一張網路,對與這張網路跑一邊最大費用最大流即可。本文使用著名又基礎的 \(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], vis[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()) { do { res += Update(); } while(vis[t]); } 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; }