这是中国历史上的一个著名故事。
大约 2300 年前,田忌是齐国的一位将军,他喜欢与国王等人赛马。
田忌和国王都有三匹不同等级的马----下马、中马、上马。
规则是一场比赛要进行三个回合,每匹马进行一回合的较量,单回合的获胜者可以从失败者那里得到 200 银元。
比赛的时候,国王总是用自己的上马对田忌的上马,中马对中马,下马对下马。
由于国王每个等级的马都比田忌的马强一些,所以比赛了几次,田忌都失败了,每次国王都能从田忌那里拿走 600 银元。
田忌对此深感不满,直到他遇到了著名的军事家孙膑,利用孙膑给他出的主意,田忌终于在与国王的赛马中取得了胜利,拿走了国王的 200 银元。
其实胜利的方法非常简单,他用下马对战国王的上马,上马对战国王的中马,中马对战国王的下马,这样虽然第一回合输了,但是后两回合都胜了,最终两胜一负,取得了比赛胜利。
如果田忌活在如今,那么他可能会嘲笑自己当初的愚蠢。
重要的是,如果他现在正参加 ACM 竞赛,他可能会发现赛马问题可以简单地看作是在二分图中找到最大匹配项。
在一侧画田忌的马,在另一侧画国王的马,只要田忌的一匹马能够击败国王的一匹马,我们就在这两匹马之间画一条边。
然后,赢尽可能多的回合,就变成了在这个二分图中找到最大匹配。
如果存在平局,问题会变得更加复杂,他需要为所有可能的边分配权重 0、1 或 −1,并找到最大加权的完美匹配…
然而,赛马问题其实是二分图匹配的一种非常特殊的情况。
该图由马的速度决定,速度快的总是能击败速度慢的。
这种情况下,加权二分图匹配算法就显得大材小用了。
在这个问题中,你需要编写一个程序,来解决这一特殊的匹配问题。
输入格式
输入包含最多 50 组测试数据。
每组数据第一行包含一个整数 n,表示一方马的数量。
第二行包含 n 个整数,是田忌的马的速度。
第三行包含 n 个整数,是国王的马的速度。
输入的最后一行为 0,表示输入结束。
输出格式
每组数据输出一个占一行的整数,表示田忌最多可以获得多少银元。
数据范围
1≤n≤1000
马的速度不超过 1000。
输入样例:
3
92 83 71
95 87 74
2
20 20
20 20
2
20 19
22 18
0
输出样例:
200
0
0
思路分析
假设田忌最快的马为f1,最慢的马为s1,国王最快的马为f2,最慢的马为s2。因为我们想让田忌赢尽可能多的比赛(温馨提示:VS代表让两人对应编号的马进行比赛)。
则有以下情况:
- s1>s2时,让s1 VS s2,此时田忌会赢一场。
- s1<s2时,让s1 VS f2,因为田忌的马s1 是最慢的马,它比国王最慢的马s2还慢,所以田忌的这匹马,无论与国王的哪匹马相比都是输的。此时可以让田忌最垃圾的马去打国王最厉害的马,即让这匹马当炮灰去把国王的马的整体速度给拉下来,这样能让田忌在接下来的比赛中能有更大的机会去赢。
- s1=s2时,田忌最慢的马和国王最慢的马速度相等,即此时田忌的马要么是输,要么是平局,因为输的话可以拿它去当炮灰把国王的马的整体速度给拉下来,这样能让田忌在接下来的比赛中能有更大的机会去赢。所以此时无法确定哪种选择是更优的。因此在此条件下再来比较最快的马看看(思考一下:为什么要比较最快的马呢?因为想在国王必赢的极端条件下用最慢的马来当炮灰)。
3.1 f1>f2时,让f1 VS f2,因为田忌最快的马f1 比国王最快的马f2都快,所以田忌这匹马是必赢的,让它去打国王最快的马,这既能赢一场比赛,又把国王的马的速度给拉下来 。
3.2 f1<f2时,让s1 VS f2,因为田忌最快的马f1 比国王最快的马f2都慢,所以国王这匹马是必赢的,此时只能让田忌最慢的马s1去当炮灰,去把国王的马的速度给拉下来 。
3.3 f1=f2时,让s1 VS f2,因为此时 s1=s2且 f1=f2。s1有两种选择
① s1 VS s2。(平局)
② s1 VS f2(因为有可能s1 = f2,所以田忌有可能输一场)。
假设情况① s1 VS s2。(平局)能取得最优解。
因为 s1 VS s2了,所以f2可以选择田忌的马中的任何一匹马进行比赛设其为x(其中x不包括 s1),此时田忌的马有如下的速度关系, f1>=x>=s1。
假设f1>x。此时田忌净输一场( s1 VS s2,平局, x VS f2,因为f2=f1>x,所以田忌输)。
我们来看看这种条件下的第二种情况,② s1 VS f2情况会如何。
此时田忌最坏情况下净输一场( s1 VS f2,田忌输, x VS s2,因为x>=s1=s2,当x=s2时平局(田忌净输一场),当x>s2时,田忌赢(两局来看田忌不赢不输))。即情况② s1 VS f2,在最坏的情况下,也不会比情况①差,且情况②有可能比它更好。
假设f1=x。此时又有两种情况。
当x=s1时,此时有 f1=f2=x=s1=s2。所以情况① s1 VS s2( s1 VS s2平局,x VS f2平局)和② s1 VS f2( s1 VS f2平局,x VS s2平局)两者等价,即两局都是平局。
当x>s1时,此时有 f1=f2=x>s1=s2。所以情况① s1 VS s2( s1 VS s2平局,x VS f2平局)和② s1 VS f2( s1 VS f2田忌输,x VS s2田忌赢)即从两局总和来看也是平局。
所以综上所述,情况② s1 VS f2在最坏的情况下也不会比情况① s1 VS s2差,即情况②一定有最优解。注意:这里不是说情况①没有最优解,只是情况①不一定有最优解。但情况②一定有最优解。即3.3 f1=f2时,让s1 VS f2能取得最优解。
注意点:是不是还会有小伙伴说我还没思考x<s1的情况。因为x>=s1所以不用考虑。
代码
#include <iostream>
#include<algorithm>
using namespace std;
int a[1005],b[1005];//a数组存储田忌的马的速度,b数组存储国王的马的速度
//判断某场比赛谁赢
int judge(int x,int y){
if(a[x]>b[y])
return 1;//田忌赢
if(a[x]<b[y])
return -1;//田忌输
return 0;//平局
}
int main()
{
int n;
while(cin>>n,n){//输入多组数据
for(int i=0;i<n;i++)//输入田忌的马的速度
cin>>a[i];
for(int i=0;i<n;i++)//输入国王的马的速度
cin>>b[i];
sort(a,a+n,greater<int>());//注意点:一定要将马的速度从大到小排序
sort(b,b+n,greater<int>());
int f1=0,f2=0,s1=n-1,s2=n-1;
int res=0;
while(f1<=s1){//每次安排两匹马进行比赛,如果还有马没安排比赛则继续循环
if(a[s1]>b[s2]){//s1>s2时,让s1 VS s2
res++;//田忌赢
s1--;
s2--;
}else if(a[s1]<b[s2]){//s1<s2时,让s1 VS f2
res--;//田忌输
s1--;
f2++;
}else{//s1=s2时,比较最快的马的情况
if(a[f1]>b[f2]){// f1>f2时,让f1 VS f2
res++;//田忌赢
f1++;
f2++;
}else{//f1<f2时和f1=f2时都是让s1 VS f2
res+=judge(s1,f2);//这两种情况田忌有输有平,要根据情况决定
s1--;
f2++;
}
}
}
cout<<res*200<<endl;//田忌最多可以获得多少银元,注意点一定要乘200
}
return 0;
}