HDU-1217-POJ-2240-Arbitrage


title: HDU-1217-POJ-2240-Arbitrage
categories:

  • ACM
  • Bellman
    tags:
  • 正环
    date: 2020-03-09 09:58:42

HDU和POJ的数据都很水,这个题没说第一个货币一定在环中,但以第一种货币一定在环中的算法却都能AC,不信试试我给的测试数据,不得不说Bellman真的强,本文给出这个题的严谨证明和算法!!!不可转载谢谢!

题目

套利

*时间限制:2000/1000 MS(Java /其他)内存限制:65536/32768 K(Java /其他)
提交总数:12754接受提交:5785
*

问题描述

套利是利用货币汇率差异将一种货币单位转换成一种以上相同货币单位。例如,假设1美元买入0.5英镑,1英镑买入10.0法国法郎,而1法国法郎买入0.21美元。然后,通过转换货币,一个聪明的交易者可以从1美元开始,买入0.5 * 10.0 * 0.21 = 1.05美元,获利5%。

您的工作是编写一个程序,该程序将货币汇率列表作为输入,然后确定是否可以进行套利。

输入值

输入文件将包含一个或多个测试用例。在每个测试用例的第一行中,有一个整数n(1 <= n <= 30),代表不同货币的数量。接下来的n行分别包含一种货币的名称。名称中不会出现空格。下一行包含一个整数m,代表要跟随的表的长度。最后的m行分别包含源货币的名称ci,代表从ci到cj的汇率的实数rij和目标货币的名称cj。没有出现在表中的交换是不可能的。
测试用例之间用空白行隔开。输入以n的零(0)值终止。

输出量

对于每个测试用例,分别以“案例:是”和“案例:否”的格式打印一行,说明是否可以套利。

Sample Input

3
USDollar
BritishPound
FrenchFranc
3
USDollar 0.5 BritishPound
BritishPound 10.0 FrenchFranc
FrenchFranc 0.21 USDollar

3
USDollar
BritishPound
FrenchFranc
6
USDollar 0.5 BritishPound
USDollar 4.9 FrenchFranc
BritishPound 10.0 FrenchFranc
BritishPound 1.99 USDollar
FrenchFranc 0.09 BritishPound
FrenchFranc 0.19 USDollar

0

Sample Output

Case 1: Yes
Case 2: No

算法

先给出不严谨的算法(第一种货币一定在环中),这个就不用证明了吧,这个在HDU、POJ都能AC!

#include
#include
#include
#include
#include
using namespace std;
int n,m; 
map y;
struct {
	int x,y;
	double h;
}  bian[5000];
double d[5000];
int c=1;
bool Bellman(){
	d[1]=1;
	bool flag;
	for(int i=1;i<=n-1;i++)
	{
		flag=0; 
		for(int j=1;j<=m;j++)
		{
			if(d[bian[j].y]>m;
		for(int i=1;i<=m;i++)
		{
			scanf("%s%lf",a,&hui);
			bian[i].x=y[a];
			bian[i].h=hui;
			scanf("%s",a);
			bian[i].y=y[a];
			
		}	
		cout<<"Case "<

测试数据

4
a
b
c
d
3
b 0.5 c
c 1 d
d 10 b
0

这个数据将第一个点孤立了,b->c->d->b为正环,按照题目逻辑应该输出Yes,但是上述不严谨算法输出No,可见HDU、POJ数据太水了,上述不严谨算法都AC了

再给出严谨的算法,有人说遍历所有起点,多次调用Bellman,NoNoNo!还是太小看Bellman的算法,本题中如果指定第一种货币为起点则应该d[1]=1,其余全为0。**严谨的做法应该是d全为1即可,但也不要生搬硬套,此题为乘法类,所以为1,如果是加法类就应该为0。**如果d全为0,则起点在正负边(比如b->c为0.5倍,这是负边;d->b为10倍,为正边)交界的顶点,有人说正正边交界的顶点也是起点,那只能说你不明白Bellman算法。将正负交界的顶点放在一个集合S中,下面我来证明,一定有一个顶点q属于S,这个点到所有点都为正边(为什么要证明这个,因为碰到负边会走不下去),现在的问题就变成了:

已知一个正环,S为正负边交界的顶点的集合,证明:一定存在点q属于S,q到此正环所有顶点的的边都为正边

将此正环相邻的相同符号的边看做一条边,即相邻的正边看做一条正边,相邻的负边看做一条负边,如下

HDU-1217-POJ-2240-Arbitrage_第1张图片

这个是个有向图,顺时针,忘了画方向。相邻的正负(先正后负)边看做一个整体,比如上图有两部分,ad和da。这个图就有两组这样的正负边,一个正环可能有n组正负边,当n==2时,由于是一个正环,则这两组边有两种情况

1.都是正边,则结论得证

2.一正一负,那么从正的那组正负边的起点作为起点,结论得证

现在我要用数学归纳法,k=2时结论成立(k为正负边的组数,就是上面的n),当k=k+1时,又有两种情况

1.都是正边,结论得证

2.存在负边,则将负边中任意一组去掉,变成k-1组正负边组合+一个负的正负边组合。因为整个环为正环,所以这k-1组也可以组成一个正环,k-1组正负边组成的正环符合结论,结论得证

所以可以证明,Bellman选择的起点集合中一定有一个点可以走遍整个环,附上代码,就改了d的初值

#include
#include
#include
#include
#include
using namespace std;
int n,m; 
map y;
struct {
	int x,y;
	double h;
}  bian[5000];
double d[5000];
int c=1;
bool Bellman(){
	//d[1]=1;
	bool flag;
	for(int i=1;i<=n-1;i++)
	{
		flag=0; 
		for(int j=1;j<=m;j++)
		{
			if(d[bian[j].y]>m;
		for(int i=1;i<=m;i++)
		{
			scanf("%s%lf",a,&hui);
			bian[i].x=y[a];
			bian[i].h=hui;
			scanf("%s",a);
			bian[i].y=y[a];
		}	
		cout<<"Case "<

你可能感兴趣的:(ACM)