【最短路】洛谷P1491集合位置题解

题干

集合位置 - 洛谷

思路

这道题讲了这么多,其实就是要求节点1到节点n次短路目标明确又简短,但怎么求这个次短路?

这一题实则还是运用最短路算法——次短路就是次于最短路的路径方案。所以,对于图G,令其最短路径边集G_1,次短路径边集为G_2;当G'=G-G_1时,G'的最短路径边集G'_1=G_2

那么,当我们找出原图的所有最短路径并“删除”,再跑一边最短路就可以找到次短路了——但怎么“删边”呢?其实并不需要删边这么麻烦,我们只需要找到最短路径边集后,跳过最短路径上相邻的两个点就好了。代码如下:

for(int i=n;i!=1;i=frt[i])//frt:用于记录最短路径,存点的前缀
{
	dijkstra(1,frt[i],i);//仍然从1开始,并跳过最短路径上的边(frt[i],i),避免题干中的“重复”
	ans=min(ans,dis[n]);//记录答案,取最小值
}

最短路的实现还是用Dijkstra吧,spfa就算了。代码如下:

void dijkstra(ll s,ll uu,ll vv)//dijkstra的板子
//这里引入两个参数(uu,vv)
//如果是(-1,-1)代表是计算原图的最短路
//否则就是计算次短路时要跳过的边(frt[i],i)
{
	for(int i=1;i<=n;i++)
	dis[i]=inf,vis[i]=0;
    dis[s]=0;
    q.push((node){0,s});
    while(!q.empty())
    {
        node tmp=q.top();
        q.pop();
        ll x=tmp.pos;
		dd d=tmp.dis;
        if(vis[x])continue;
        vis[x]=1;
        for(int i=head[x];i;i=e[i].next)
        {
            ll y=e[i].to;
        	if((x==uu&&y==vv)||(x==vv&&y==uu))continue;//跳过要删除的边
            if(dis[y]>dis[x]/*d*/+e[i].w)
			{
				dis[y]=dis[x]/*d*/+e[i].w;
				if(uu==-1&&vv==-1)frt[y]=x;//用前缀形式存原图最短路
				q.push((node){dis[y],y});
            }
        }
    }
}

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define dd double
const ll z=3e6+9,mod=100003;
const dd inf=2e9;
dd x,y,dis[z],ans;
ll n,m,u,v,frt[z];
bool vis[z];
struct point
{
	dd x,y;
}p[z];
struct edge
{
	ll to,next;
	dd w;
}e[z*2];
ll head[z*2],idx;
void addedge(ll u,ll v,dd w)
{
	idx++;
	e[idx].to=v;
	e[idx].next=head[u];
	e[idx].w=w;
	head[u]=idx;
}
struct node
{
	dd dis;
	ll pos;
    bool operator<(const node &x)const
    {
        return x.dis<dis;
    }
};
priority_queue<node>q;
void dijkstra(ll s,ll uu,ll vv)
{
	for(int i=1;i<=n;i++)
	dis[i]=inf,vis[i]=0;
    dis[s]=0;
    q.push((node){0,s});
    while(!q.empty())
    {
        node tmp=q.top();
        q.pop();
        ll x=tmp.pos;
		dd d=tmp.dis;
        if(vis[x])continue;
        vis[x]=1;
        for(int i=head[x];i;i=e[i].next)
        {
            ll y=e[i].to;
        	if((x==uu&&y==vv)||(x==vv&&y==uu))continue;
            if(dis[y]>dis[x]/*d*/+e[i].w)
			{
				dis[y]=dis[x]/*d*/+e[i].w;
				if(uu==-1&&vv==-1)frt[y]=x;
				q.push((node){dis[y],y});
            }
        }
    }
}
int main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%lf%lf",&x,&y);
		p[i]=point{x,y};
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%lld%lld",&u,&v);
		dd o=sqrt((p[u].x-p[v].x)*(p[u].x-p[v].x)+(p[u].y-p[v].y)*(p[u].y-p[v].y));
	//	printf("%lf\n",o);
		addedge(u,v,o);
		addedge(v,u,o);
	}
	dijkstra(1,-1,-1);
//	printf("%lf\n",dis[n]);
	ans=inf;
	for(int i=n;i!=1;i=frt[i])
	{
	//	cout<<i<<endl;
		dijkstra(1,frt[i],i);
		ans=min(ans,dis[n]);
	}
	if(ans==inf)printf("-1");
	else printf("%.2lf",ans);
	return 0;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值