POJ--3521[Geometric Map] 几何图上的最短路

45 篇文章 0 订阅
8 篇文章 1 订阅

题目大意:
给你一幅图,上面有一些“街道”和“标记边”组成。问你某两点之间的最短路径。(“标记边”用来规定“街道”的方向)。

 

街道:表示两点之间有边相连。
标记边:标记边的一端悬空,令一端和一条道路相交(相交一定在中间,不会在道路端点处)。
标记规则:假设街道的向量为(a,b);标记边的向量为(c,d).且c在线段[a,b]上。如果向量(a,b)于向量(c,d)的夹角<=90,则<a,b>方向不可行。反之<a,b>方向可行。

 

注意:
(1):一条标记边有且且仅标记一条街道。
(2):一条街道可以被多条标记边标记。
(3):街道的端点一定连另外一条或多条街道,不会连标记边。


关键语句:
(1):In general, an end point of a sign touches one and only one line segment representing a street and the other end point is open.
(2):Each end point of every street touches one or more streets, but no signs.

 


PS.思路很简单,基本上是纯模拟的,但是各种预处理,各种麻烦。做了一晚上,最后终于找出bug,太恶心了。这种几何题吃不消啊。

 

CODE:

/*几何图上的最短路*/
/*AC代码:32ms*/
#include <iostream>
#include <cstdio>
#include <memory.h>
#include <algorithm>  
#include <map>
#include <queue>
#include <cmath>
#define INF 1e8 
#define eps 1e-6
#define MAXN 605
using namespace std;
struct Node
{
	int x,y; 
};
struct cmp
{
	bool operator()(const Node &a,const Node &b) const
	{
		if(a.x!=b.x)
			return a.x<b.x;
		else
			return a.y<b.y;
	}
};
Node s,e,node[MAXN];
int Map[MAXN][MAXN];
double E[MAXN][MAXN];//最后构建的图
int G[1005][1005];
int deg[MAXN],ans[MAXN];
int pre[MAXN];
double dis[MAXN];
bool vis[MAXN],mark[MAXN];
map<Node,int,cmp>hash;
int cnt;//表示总的点数
int scr,sink,N;
void ini()
{
	hash.clear();
	memset(Map,0,sizeof(Map));
	memset(G,0,sizeof(G));
	memset(deg,0,sizeof(deg));
	memset(mark,true,sizeof(mark));
	cnt=0;
}
int gcd(int n,int m)//求最大公约数
{
	if(m==0) return n;
	return gcd(m,n%m);
}
int xmulti(Node p1,Node p2,Node p0)//叉积
{
	return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);
}
int dmulti(Node p1,Node p2)//点积
{
	return p1.x*p2.x+p1.y*p2.y;
}
double get_dis(int a,int b)//返回两点距离的平方
{
	return sqrt((double)((node[a].x-node[b].x)*(node[a].x-node[b].x)+(node[a].y-node[b].y)*(node[a].y-node[b].y)));
}
int ponls(Node a,Node b,Node p)//判断点p是否在线段a,b上 
{
	return ((xmulti(b,p,a)==0)&&(((p.x-a.x)*(p.x-b.x)<0)||((p.y-a.y)*(p.y-b.y)<0)));
}

void f(Node now)
{
	int i,j;
	bool ok=false;
	hash[now]=++cnt;
	//now.id=cnt;
	node[cnt]=now;
	for(i=1;i<=cnt&&!ok;i++)
	{
		for(j=i+1;j<=cnt;j++)
		{
			if(Map[i][j]&&ponls(node[i],node[j],now))
			{
				Map[i][j]=Map[j][i]=0;
				Map[i][cnt]=Map[cnt][i]=1;
				Map[j][cnt]=Map[cnt][j]=1;
				deg[cnt]=3;
				ok=true;
				break; 
			}
		}
	}
	if(!ok)
	{
		deg[cnt]=1;
	}	
}
void Run()
{
	int i,dx,dy,nx,ny,w;
	Node a,b,c;
	scanf("%d%d%d%d",&a.x,&a.y,&b.x,&b.y);
	if(!hash[a])
	{
		f(a);
	}
	else
		deg[hash[a]]++;
	if(!hash[b])
	{
       	f(b);		
	}
	else
		deg[hash[b]]++;
	dx=abs(a.x-b.x);
	dy=abs(a.y-b.y);
	w=gcd(dx,dy);
	dx=(b.x-a.x)/w;
	dy=(b.y-a.y)/w;
	//遍历所以点,判断是否有点落在新输入的边上(端点除外)
	int pre=hash[a];
	int after=hash[b];
	for(i=1;i<w;i++)
	{
		nx=a.x+dx*i;
		ny=a.y+dy*i;
		if(G[nx][ny])
		{
			c.x=nx;
			c.y=ny;
			int id=hash[c];
			deg[id]+=2;
			Map[pre][id]=Map[id][pre]=1;
			pre=id;
		}
	}
	Map[pre][after]=Map[after][pre]=1;
	G[a.x][a.y]=G[b.x][b.y]=1;
}
int go(int ith,int id,int k)
{
	Node p1,p2;
	p1.x=node[ith].x-node[id].x;
	p1.y=node[ith].y-node[id].y;
	p2.x=node[k].x-node[id].x;
	p2.y=node[k].y-node[id].y;
	int res=dmulti(p1,p2);
	//if(res>=0)//表示这个方向行不通
	//	Map[id][k]=0; 
	if(res>0) return 1;
	if(res==0) return 0;
	return -1;
}
int dfs(int pre,int now)
{
	int i;
	if(mark[now]) return now;
	for(i=1;i<=cnt;i++)
	{
		if(i==pre) continue;
		if(Map[now][i]==1 && deg[i]>1)
			return dfs(now,i);
	}
}
void fuck(int ith)
{
	int i,id,a,b;
	for(i=1;i<=cnt;i++)
	{
		if(Map[ith][i]==1&&i!=ith)//处理标记
		{id=i;break;}
	}
	mark[ith]=mark[id]=false;
	int k=0;
	for(i=1;i<=cnt;i++)
	{
		if(Map[id][i]==1&&i!=id && deg[i]>1)
		{
			//注意这里第一个找到的不一定是路的端点,可能是标记端点,所以要递归的找
			if(k==0) 
			{a=dfs(id,i);k++;}
			else
			{b=dfs(id,i);k++;break;} 
			//go(ith,id,i);
		}
	}
	//if(k==0) while(1);
	int res=go(ith,id,b);
	if(res==0) 
		Map[a][b]=Map[b][a]=-1;
	else if(res<0)
	{
		Map[b][a]=-1;
		if(Map[a][b]!=-1)
			Map[a][b]=1;
	}
	else
	{
		Map[a][b]=-1;
		if(Map[b][a]!=-1)
			Map[b][a]=1; 
	} 
}
void Build()
{
	int i,j;
	//先把所有标记点mark为false
	for(i=1;i<=cnt;i++)
	{
		if(deg[i]==1)
		{
			mark[i]=false;
			for(j=1;j<=cnt;j++)
			{
				if(Map[i][j]==1)
				{mark[j]=false;break;}
			}
		}
	}
	//遍历所以点,找出度为1的点进行处理
	for(i=1;i<=cnt;i++)
	{
		if(deg[i]==1)
			fuck(i);
	}
	for(i=1;i<=cnt;i++)
	{
		for(j=1;j<=cnt;j++)
		{
			if(i==j||!mark[i]||!mark[j]||Map[i][j]==-1||Map[i][j]==0) 
				E[i][j]=-1;
			else
				E[i][j]=get_dis(i,j); 
		} 
	}
}
queue<int>Q;
void SPFA(int s,int e)//找最短路输出答案
{
	int i,u,v,w;
	while(!Q.empty()) Q.pop();
	memset(vis,false,sizeof(vis));
	memset(pre,-1,sizeof(pre));
	for(i=1;i<=cnt;i++)
		dis[i]=INF;
	Q.push(s);
	vis[s]=true;
	dis[s]=0;
	while(!Q.empty())
	{
		u=Q.front();Q.pop();
		vis[u]=false;
		for(i=1;i<=cnt;i++)
		{
			if(E[u][i]==-1) continue;
			if(dis[i]>dis[u]+E[u][i])
			{
				dis[i]=dis[u]+E[u][i];
				pre[i]=u;
				if(!vis[i])
				{
					vis[i]=true;
					Q.push(i);
				}
			}
		}
	}
	if(dis[e]==INF)
		printf("-1\n");
	else
	{
		int num=0;
		u=e;
		while(true)
		{
			ans[num++]=u;
			u=pre[u];
			if(u==-1) break;
		}
		for(i=num-1;i>=0;i--)
		{
			printf("%d %d\n",node[ans[i]].x,node[ans[i]].y);
		}
		printf("0\n");
	}
}
void Print()
{
	int i;
	for(i=1;i<=cnt;i++)
		printf("%d %d\n",node[i].x,node[i].y);
	printf("****\n");
}
void Solve()
{
	int i;
	ini();
	scanf("%d%d%d%d",&s.x,&s.y,&e.x,&e.y);
	for(i=1;i<=N;i++)
	{
		Run();
	}
	scr=hash[s];
	sink=hash[e];
	Build();//构图
	SPFA(scr,sink);//找最短路
	//printf("^%d %d\n",scr,sink);
	//Print();
}
int main()
{
	while(scanf("%d",&N),N)
	{
		Solve();
	}
return 0;
}
/*
7
0 0 2 3
0 0 3 0
3 0 3 3
3 3 2 3
0 3 2 3
0 0 0 3
1 3 0 4
1 3 2 2

0 0
3 0
3 3
2 3
0
*/
/*
8
0 0 2 3
0 0 3 0
3 0 3 3
3 3 2 3
0 3 2 3
0 0 0 3
1 3 0 4
1 3 2 2
2 2 3 2

0 0
0 3
2 3
0
*/
/*
7
0 0
4 4
0 0 5 0
5 0 5 4
0 0 0 4
0 4 4 4
4 4 5 4
2 4 1 3
3 4 2 3

0 0
0 4
4 4
0
*/

 


 

 


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

__简言

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值