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