算法竞赛入门经典——高效算法——巨人与鬼

巨人与鬼:

题目:

一组n个巨人正与n个鬼进行战斗,每个巨人的武器是一个质子炮, 它可以把一串质子流射中鬼而把鬼消灭。质子流沿直线行进,在击中鬼时就终止。巨人决定采取下述策略。他们寻找鬼配对,以形成n个巨人─鬼对,。然后每个巨人同时向他选取的鬼射出一串质子流。我们知道,让质子流互相交叉是很危险的。因此巨人选择的配对方式应该使质子流都不会交叉。假定每个巨人和每个鬼的位置都是平面上的一个固定点,并且没有三个位置共线, 求一种配对方案。

下面的输入是笔者自己设计的:

输入:

有多组输入,第一行为n,第二行为n个巨人的坐标(浮点型),第三行为n个巨人的坐标(浮点型)

输出:

巨人编号(这里的编号就是各坐标输入的次序0——2*n-1)--鬼的编号。

样例输入:

2
0 0 1 2
2 1 -1 4

样例输出(有可能有多种匹配方案,只要符合要求即可):

0--2

1--3

这一题书上说是分治+递归,但是笔者刚开始只是当成数理题做,并没有意识到其分治的思想,下面的代码也是按照笔者最开始的数理方法写的,分治思想的代码之后再补上。

1 .首先,要如何想到这种极坐标的结构呢?事实上,可以这么想:对于一个巨人A,比如下图远点位置的蓝圆圈,总要有一个鬼和它配对(题中说n个鬼,n个巨人),因此,现在我们要在n个鬼当中选1个鬼给巨人A怼,这样就很自然地想到以该巨人A为原点,鬼们绕着这个巨人,这样再来考虑如何选鬼被巨人A怼会直观一些,考虑到题目中说,巨人选择的配对方式应该使质子流都不会交叉,因此我们还要考虑其它巨人的空间位置。

2. 如果选图中鬼1,接下去巨人B——鬼2,巨人C——鬼3,看似没什么毛病,但是要注意,如果我们选巨人A——鬼1,那么巨人B——鬼2很可能与质子流巨人A——鬼1相撞,那么怎么办呢,有没有一种优美的方法能天然地避开这种撞击问题呢?诶,还真有:从下图中的x轴正向出发,沿着逆时针方向找到第一个安全鬼(解释一下安全鬼:在下图中就是鬼2,因为鬼1前面还有一个巨人B,为了避免B的质子流与A的质子流相撞,安全起见,我们把鬼1让给B,找下一个鬼怼,所以如果鬼1和鬼2之间还有一个巨人E,那么鬼2也得让给巨人B、E,找下一个安全鬼。)


        

3.有些细心的同学就会问:我们该从哪个巨人开始匹配,好,给大家一点时间捋一捋,想一想:


好,我们该从哪个巨人开始匹配呢?答案是:从最底下的那个巨人开始匹配,为什么呢?请看下图:

如果我们不从位于最下面的巨人B开始匹配,而从上面一点A的开始匹配,采用刚才上面讨论的方法,得到:巨人A——鬼1,好,这样就会有问题了,巨人B潜伏在下面,很可能就会一箭射穿巨人A——鬼1质子流,但是如果我们每次都从最下面的巨人开始匹配的话,就排除了有潜在巨人在x轴下面的隐患,当然鬼是可以在x轴下面的。



4. 好,基本策略我们已经弄清了,现在代码要怎么写呢?不,应该问伪代码要怎么写呢?来,借一步说话——>

          1.找到当前最下面的巨人A,且该巨人A没被访问过

          2.以该巨人A为原点,以逆时针方向(这里涉及到按角度排序)找到第一个未被访问的安全鬼,匹配之

          3.标记该巨人A和匹配的鬼被访问过。

          4.如果还有巨人未匹配,转1.

5. OK,基本伪代码已经有了,直接上代码吧,建议大家自己先写。

#include<iostream>
#include<math.h>
#include<algorithm>
using namespace std;
#define Maxsize 1005
#define INF 10000000
/*
INPUT EXAMPLE:
2
0 0 1 2
2 1 -1 4
3
0 0 2 2 1 3
3 0 -1 4 -2 1
*/

struct Monster
{
	int ind;
	float x;
	float y;
	int cat;//category: giant(1) or gost(0).
}A[Maxsize], Angle[Maxsize];
int visit[Maxsize];
bool cmp(Monster a, Monster b)
{
	float x1 = a.x;
	float y1 = a.y;
	float x2 = b.x;
	float y2 = b.y;


	if (x1*x2< 0)//
	{
		return x1 > 0 ? 1 : 0;//negtive one ranks later
	}
	else
	{
		if (x1 < 0)//x1 and x2 are both  negative.
		{
			x1 = -x1;
			x2 = -x2;
			return x2*y1 > y2*x1;//for negative:sorted by abs(y/x),larger one ranks front.
		}
		else
			return x2*y1 < y2*x1;
	}

}
void SortByAngle(int centerind,int n)
{
	int i,k=0;
	float centerx, centery;
	for (i = 0;i <  n;i++)
		if (visit[A[i].ind]==0&&A[i].ind == centerind)
		{
			centerx = A[i].x;
			centery = A[i].y;
			break;
		}
	for (i = 0;i <  n;i++)
	{
		if (visit[A[i].ind] == 0&&A[i].ind != centerind)
		{
			//In fact Angle plays a role of temp array.
			Angle[k].ind = A[i].ind;
			Angle[k].cat = A[i].cat;
			Angle[k].x= A[i].x-centerx;
			Angle[k++].y = A[i].y - centery;
		}
	}
	sort(Angle, Angle + k, cmp);
	

}
int main()
{
    //Input
	int n,i;//n:the number of stg(or gnt).
	while (cin >> n)
	{
		//Initialize the visit
		memset(visit, 0, sizeof(visit));
		//Input giants' position
		for (i = 0;i < n;i++)
		{
			cin >> A[i].x >> A[i].y;
			A[i].ind = i;
			A[i].cat = 1;
		}
		//Input ghosts' position 
		for (i = n;i < 2*n;i++)
		{
			cin >> A[i].x >> A[i].y;
			A[i].ind = i;
			A[i].cat = 0;
		}
		/* check whether the input is correct.
		for (i = 0;i < 2 * n;i++)
			cout << A[i].ind << " " << A[i].x << " " << A[i].y << endl;
		*/	
		int s = 2*n;
		int fstmgst = 0;
		while(s)
		{ 
			//select the centerind;
			float miny = INF;
			int centerind;
			for (i = 0;i < 2*n;i++)
				if(visit[A[i].ind]==0)
			{
				if (A[i].cat==1&&miny > A[i].y)
				{
					miny = A[i].y;
					centerind = A[i].ind;
				}
			}
				
			//sort A and then asign to angle.
			SortByAngle(centerind, 2 * n);

			for (i = 0;i < s;i++)
				if(visit[Angle[i].ind]==0)
		    {
				if (Angle[i].cat == 0 && fstmgst == 0)
				{
					//match centerind and Angle[i].ind
					cout << centerind << "--" << Angle[i].ind<<endl;

					visit[centerind] = 1;
					visit[Angle[i].ind] = 1;
					break;
				}
				else
				{
					if(Angle[i].cat==1)
					    fstmgst++;
					else
					{
						fstmgst--;
					}

				}
			}//for

			s -= 2;
		}
	}

	system("pause");
	return 0;
}

如有错误,希望各位踊跃指出。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值