★☆【二分圖最佳匹配】丘比特的煩惱

来源:互联网 发布:6月淘客不计入淘宝权重 编辑:程序博客网 时间:2024/06/10 16:29
随着社会的不断发展,人与人之间的感情越来越功利化。最近,爱神丘比特发现,爱情也已不再是完全纯洁的了。这使得丘比特很是苦恼,他越来越难找到合适的男女,并向他们射去丘比特之箭。于是丘比特千里迢迢远赴中国,找到了掌管东方人爱情的神——月下老人,向他求教。  月下老人告诉丘比特,纯洁的爱情并不是不存在,而是他没有找到。在东方,人们讲究的是缘分。月下老人只要做一男一女两个泥人,在他们之间连上一条红线,那么它们所代表的人就会相爱——无论他们身处何地。而丘比特的爱情之箭只能射中两个距离相当近的人,选择的范围自然就小了很多,不能找到真正的有缘人。  丘比特听了月下老人的解释,茅塞顿开,回去之后用了人间的最新科技改造了自己的弓箭,使得丘比特之箭的射程大大增加。这样,射中有缘人的机会也增加了不少。  情人节(Valentine's day)的午夜零时,丘比特开始了自己的工作。他选择了一组数目相等的男女,感应到他们互相之间的缘分大小,并依此射出了神箭,使他们产生爱意。他希望能选择最好的方法,使被他选择的每一个人被射中一次,且每一对被射中的人之间的缘分的和最大。  当然,无论丘比特怎么改造自己的弓箭,总还是存在缺陷的。首先,弓箭的射程尽管增大了,但毕竟还是有限的,不能像月下老人那样,做到“千里姻缘一线牵”。其次,无论怎么改造,箭的轨迹终归只能是一条直线,也就是说,如果两个人之间的连线段上有别人,那么莫不可向他们射出丘比特之箭,否则,按月下老人的话,就是“乱点鸳鸯谱”了。  作为一个凡人,你的任务是运用先进的计算机为丘比特找到最佳的方案。输入文件格式:  输入文件第一行为正整数k,表示丘比特之箭的射程,第二行为正整数n(n<30),随后有2n行,表示丘比特选中的人的信息,其中前n行为男子,后n行为女子。每个人的信息由两部分组成:他的姓名和他的位置。姓名是长度小于20且仅包含字母的字符串,忽略大小写的区别,位置是由一对整数表示的坐标,它们之间用空格分隔。格式为Name x y。输入文件剩下的部分描述了这些人的缘分。每一行的格式为Name1 Name2 p。Name1和Name2为有缘人的姓名,p是他们之间的缘分值(p为小于等于255的正整数)。以一个End作为文件结束标志。每两个人之间的缘分至多只被描述一次。如果没有被描述,则说明他们缘分值为1。输出文件格式:  输出文件仅一个正整数,表示每一对被射中的人之间的缘分的总和。这个和应当是最大的。输入样例(cupid.in):230 0 Adam1 1 Jack0 2 George1 0 Victoria0 1 Susan1 2 CathyAdam Cathy 100Susan George 20George Cathy 40Jack Susan 5Cathy Jack 30Victoria Jack 20Adam Victoria 15End输出样例(cupid.out):65
這是一道最佳匹配問題,用KM算法可以解決。

首先建圖,若兩點距離大於d或連線中有第三者,則權值賦為負無窮,保證這條邊永遠不能被取到。

具體細節見程序注釋。

Accode:

#include <cstdio>#include <iostream>#include <cstring>#include <cstdlib>#include <bitset>#include <map>using namespace std;const char fi[] = "cupid.in";const char fo[] = "cupid.out";const int maxN = 50;const int MAX = 0x3fffff00;const int MIN = -MAX;map <string, int> _boy, _girl;int w[maxN][maxN];int lx[maxN], ly[maxN];int bx[maxN], by[maxN];int gx[maxN], gy[maxN];int Link[maxN];bitset <maxN> boy, girl;int n, d, t;string str, str1, str2;  void init_file()  {    freopen(fi, "r", stdin);    freopen(fo, "w", stdout);    std::ios::sync_with_stdio(false);  }  inline bool line(int x1, int y1,    int x2, int y2, int x3, int y3)  {    int _x1 = x2 - x1;    int _y1 = y2 - y1;    int _x2 = x2 - x3;    int _y2 = y2 - y3;    if (_x1 * _y2 != _y1 * _x2)      return false;    if (_x1 * _x2 + _y1 * _y2 < 0)      return true;    return false;  } //判斷(x2, y2)是否在(x1, y1)與(x3, y3)的連線上。  inline int sqr(int x) {return x * x; }  inline void ltu(string &str)  {    string::iterator iter;    for (iter = str.begin();      iter != str.end(); ++iter)     if ((*iter) >= 'a' && (*iter) <= 'z')      (*iter) -= 'a' - 'A';  } //小寫轉大寫。  void readdata()  {    cin >> d >> n;    for (int i = 1; i < n + 1; ++i)    {      cin >> bx[i] >> by[i] >> str;      ltu(str);      _boy.insert(make_pair(str, i));    }    for (int j = 1; j < n + 1; ++j)    {      cin >> gx[j] >> gy[j] >> str;      ltu(str);      _girl.insert(make_pair(str, j));    }    while (1)    {      cin >> str1 >> str2 >> t;      ltu(str1); ltu(str2);//由於題目中忽略大小寫,所以將小寫全部轉化為大寫。      if (str1 == "END") break;      if (_boy.find(str1) == _boy.end())        swap(str1, str2);      w[_boy[str1]][_girl[str2]] = t;    }  }  void Modify()  {    for (int i = 1; i < n + 1; ++i)      lx[i] = MIN;//頂標初始為負無窮。    for (int i = 1; i < n + 1; ++i)     for (int j = 1; j < n + 1; ++j)      {        if (!w[i][j]) w[i][j] = 1;        if (sqr(bx[i] - gx[j])          + sqr(by[i] - gy[j]) > sqr(d))          {w[i][j] = MIN; continue; }        if (w[i][j] > 0)        {          for (int i1 = 1; i1 < n + 1; ++i1)           if (i != i1)            if (line(bx[i], by[i], bx[i1],              by[i1], gx[j], gy[j]))              {w[i][j] = MIN; break; }        } else continue;//枚舉查看連線中間是否有其他男生。        if (w[i][j] > 0)        {          for (int j1 = 1; j1 < n + 1; ++j1)           if (j != j1)            if (line(bx[i], by[i], gx[j1],              gy[j1], gx[j], gy[j]))              {w[i][j] = MIN; break; }        } else continue;//枚舉查看連線中間是否有其他女生。        lx[i] = max(lx[i], w[i][j]);//更新頂標。      }  }  bool Find(int u)  {    boy.set(u);    for (int v = 1; v < n + 1; ++v)     if (lx[u] + ly[v] == w[u][v]      && !girl.test(v))      {        girl.set(v);        if (!Link[v] || Find(Link[v]))          {Link[v] = u; return true; }      }    return false;  }  void work()  {    for (int k = 1; k < n + 1; ++k)     while (1)      {        boy.reset();        girl.reset();        if (Find(k)) break;        int Min = MAX;        for (int i = 1; i < n + 1; ++i)         if (boy.test(i))          for (int j = 1; j < n + 1; ++j)           if (!girl.test(j))            Min = min(Min, lx[i]              + ly[j] - w[i][j]);        for (int i = 1; i < n + 1; ++i)        {          if (boy.test(i)) lx[i] -= Min;          if (girl.test(i)) ly[i] += Min;        }      }    int ans = 0;    for (int j = 1; j < n + 1; ++j)      ans += w[Link[j]][j];    printf("%d", ans);  }int main(){  init_file();  readdata();  Modify();  work();  exit(0);}

第二次做:

#include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <string>#include <map>using namespace std;const char fi[] = "cupid.in";const char fo[] = "cupid.out";const int maxN = 40;const int MAX = 0x3f3f3f3f;const int MIN = ~MAX;struct vec{    int x, y;    vec() {}    vec(int x, int y): x(x), y(y) {}    vec operator-(const vec &b) const    {return vec(x - b.x, y - b.y);}    int operator+(const vec &b) const    {return x * b.x + y * b.y;}    int operator*(const vec &b) const    {return x * b.y - y * b.x;}    int norm() const {return x * x + y * y;}    bool btwn(const vec &A, const vec &B) const    {        vec OA = vec(A.x - x, A.y - y),        OB = vec(B.x - x, B.y - y);        return !(OA * OB) && OA + OB < 0;    } };map <string, int> _boy, _girl;vec boy[maxN], girl[maxN];bool b[maxN], g[maxN];int lx[maxN], ly[maxN], Link[maxN];int mp[maxN][maxN], n, K;void init_file(){    freopen(fi, "r", stdin);    freopen(fo, "w", stdout);    return;}void readdata(){    cin >> K >> n;    for (int i = 0; i < n; ++i)    {        int x, y; string str;        cin >> x >> y >> str;        boy[i] = vec(x, y);        transform(str.begin(), str.end(),                  str.begin(), ::tolower);//将一个string类型转换为小写。_boy[str] = i;    }    for (int i = 0; i < n; ++i)    {        int x, y; string str;        cin >> x >> y >> str;        girl[i] = vec(x, y);        transform(str.begin(), str.end(),                  str.begin(), ::tolower);        _girl[str] = i;        Link[i] = -1;    }    int w; string s1, s2;    while (cin >> s1 >> s2 >> w)    {        transform(s1.begin(), s1.end(),                  s1.begin(), ::tolower);        transform(s2.begin(), s2.end(),                  s2.begin(), ::tolower);if (_boy.find(s1) == _boy.end())            swap(s1, s2);        mp[_boy[s1]][_girl[s2]] = w;    }    return;}void modify(){    for (int i = 0; i < n; ++i)    for (int j = 0; j < n; ++j)    {        if (!mp[i][j]) mp[i][j] = 1;        if ((boy[i] - girl[j]).norm() > K * K)        {mp[i][j] = MIN; continue;}        if (mp[i][j] > 0)        for (int i1 = 0; i1 < n; ++i1)        if (i1 != i && boy[i1].btwn(boy[i], girl[j]))        {mp[i][j] = MIN; break;}        if (mp[i][j] > 0)        for (int j1 = 0; j1 < n; ++j1)        if (j1 != j && girl[j1].btwn(boy[i], girl[j]))        {mp[i][j] = MIN; break;}        lx[i] = max(lx[i], mp[i][j]);    } //当不能连边时一定要把权值设为负无穷。    return;}bool Find(int i){    b[i] = 1;    for (int j = 0; j < n; ++j)    if (lx[i] + ly[j] == mp[i][j] && !g[j])    {        g[j] = 1;        if (Link[j] == -1 || Find(Link[j]))        {            Link[j] = i;            return 1;        }    }    return 0;}void work(){    for (int k = 0; k < n; ++k)    while (1)    {        memset(b, 0, sizeof b);        memset(g, 0, sizeof g);        if (Find(k)) break;        int Min = MAX;        for (int i = 0; i < n; ++i) if (b[i])        for (int j = 0; j < n; ++j) if (!g[j])            Min = min(Min, lx[i] + ly[j] - mp[i][j]);        for (int i = 0; i < n; ++i)        {            if (b[i]) lx[i] -= Min;            if (g[i]) ly[i] += Min;        }    }    int ans = 0;    for (int j = 0; j < n; ++j)        ans += mp[Link[j]][j];    printf("%d\n", ans);    return;}int main(){    init_file();    readdata();    modify();    work();    return 0;}