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

随着社会的不断发展,人与人之间的感情越来越功利化。最近,爱神丘比特发现,爱情也已不再是完全纯洁的了。这使得丘比特很是苦恼,他越来越难找到合适的男女,并向他们射去丘比特之箭。于是丘比特千里迢迢远赴中国,找到了掌管东方人爱情的神——月下老人,向他求教。
  月下老人告诉丘比特,纯洁的爱情并不是不存在,而是他没有找到。在东方,人们讲究的是缘分。月下老人只要做一男一女两个泥人,在他们之间连上一条红线,那么它们所代表的人就会相爱——无论他们身处何地。而丘比特的爱情之箭只能射中两个距离相当近的人,选择的范围自然就小了很多,不能找到真正的有缘人。
  丘比特听了月下老人的解释,茅塞顿开,回去之后用了人间的最新科技改造了自己的弓箭,使得丘比特之箭的射程大大增加。这样,射中有缘人的机会也增加了不少。
  情人节(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):
2
3
0 0 Adam
1 1 Jack
0 2 George
1 0 Victoria
0 1 Susan
1 2 Cathy
Adam Cathy 100
Susan George 20
George Cathy 40
Jack Susan 5
Cathy Jack 30
Victoria Jack 20
Adam Victoria 15
End

输出样例(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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值