图论(最短路专题):P5304 旅行者

问题描述

给你一张\(n\)\(n \leq 100,000\))个点\(m\)\(m \leq 500,000\))条边,边权为\(w_i\)的图

给你\(k\)个特殊点

求这\(k\)个点两两最短路的最小值

思路分析

给出一种时间复杂度比较低的暴力做法 (反向图染色法考场上爆0了555)

思路1:(20pts)

只考虑测试点1,2 此时\(n \leq 1000\)\(m \leq 5000\)

首先有一个显而易见的思路:

\(floyd\)全源最短路 计算每两个点间的距离

接着成对枚举\(k\)个点 用\(ans\)变量存储\(k\)个点两两最短路的最小值

问题在于该算法复杂度为\(O(n^3)\) 对于\(n \leq 1000\)很有可能会爆时间

所以修正思路 \(O(n^3)\)复杂度的\(floyd\)全源最短路修改为枚举每个点然后跑\(dijkstra\)的单源最短路

时间复杂度降低为\(O(n^2logn)\) 可以在\(1s\)的时间限制内通过此题

//20pts暴力算法(适用于n<1000)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+10;
struct node
{
	int dis,u;
	bool operator>(const node &a) const
	{
		return dis>a.dis;
	}
};
struct edge
{
	int v,w;
	int idx;
};
vector<edge> e[N];
int dis[1010][1010];
bool vis[N];
int loved[N];
void dijkstra(int s)
{
	memset(dis[s],0x3f,sizeof(dis[s]));
	dis[s][s]=0;
    priority_queue<node,vector<node>,greater<node> >q;
	q.push({0,s});
	while(!q.empty())
	{
		int u=q.top().u;
		q.pop();
		if(vis[u])continue;
		vis[u]=1;
		for(auto ed:e[u])
		{
			int w=ed.w,v=ed.v;
			if(dis[s][v]>dis[s][u]+w)
			{
				dis[s][v]=dis[s][u]+w;
				q.push({dis[s][v],v});
			}
		}
	}
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int t;
    cin>>t;
    while(t--)
    {
        int n,m,k;
        cin>>n>>m>>k;
        for(int i=0;i<N;++i)
       	 e[i].clear(); 	
        for(int i=1;i<=m;++i)
        {
        	int u,v,w;
        	cin>>u>>v>>w;
        	e[u].push_back({v,w,i});
		}
		for(int i=1;i<=n;++i)
		{
			for(int j=1;j<=n;++j)
				vis[j]=0;
			dijkstra(i);
		}
		int ans=LONG_LONG_MAX;
		for(int i=1;i<=k;++i)
			cin>>loved[i];	
		for(int i=1;i<=k;++i)
			for(int j=i+1;j<=k;++j)
			{
				ans=min(ans,dis[loved[i]][loved[j]]);
				ans=min(ans,dis[loved[j]][loved[i]]);
			}	
		cout<<ans<<"\n";
    }
    return 0;
}

思路2:(50pts)

显然思路一的时间复杂度可以得到更多的分 原因如下:

  • 本题时间限制为\(5s\) 给的很宽松 20pts算法\(O(n^2logn)\)的复杂度\(n\)大小不极端的情况下是可以通过的

  • 刚刚枚举了所有点跑\(dijkstra\)实际上只需要枚举\(k\)个已选的点 其他点完全没有必要关注的必要

考虑清楚了这两个原因 接下来就可以进行代码的修改

原代码空间复杂度为\(O(n^2)\) 对于\(n>1000\)的数据显然会爆

所以考虑将 \(ans\)更新和\(dijkstra\)合并在一个语句块内实行

这时完成上面的两个改进点 空间复杂度就可以锐减到\(O(n)\) 时间复杂度优化为\(O(nklogn)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+10;
struct node
{
	int dis,u;
	bool operator>(const node &a) const
	{
		return dis>a.dis;
	}
};
struct edge
{
	int v,w;
	int idx;
};
vector<edge> e[N];
int dis[N];
bool vis[N],iscity[N];
int loved[N];
void dijkstra(int s)
{
	memset(dis,0x3f,sizeof(dis));
	dis[s]=0; priority_queue<node,vector<node>,greater<node> >q;
	q.push({0,s});
	while(!q.empty())
	{
		int u=q.top().u;
		q.pop();
		if(vis[u])continue;
		vis[u]=1;
		for(auto ed:e[u])
		{
			int w=ed.w,v=ed.v;
			if(dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				q.push({dis[v],v});
			}
		}
	}
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int t;
    cin>>t;
    while(t--)
    { 
        int n,m,k;
        cin>>n>>m>>k;
        for(int i=1;i<=n;++i)
        	iscity[i]=0;
        for(int i=0;i<N;++i)
         	e[i].clear();
        for(int i=1;i<=m;++i)
        {
        	int u,v,w;
        	cin>>u>>v>>w;
        	e[u].push_back({v,w,i});
		}
		int ans=LONG_LONG_MAX;
		for(int i=1;i<=k;++i)
		{
			cin>>loved[i];
			iscity[loved[i]]=1;
		}
		for(int i=1;i<=n;++i)
		{
			if(!iscity[i]) continue;
			memset(vis,0,sizeof(vis));
			dijkstra(i);
			for(int j=1;j<=k;++j)
			{
				if(i==loved[j]) continue;
				ans=min(ans,dis[loved[j]]);	
			}
		}
		cout<<ans<<"\n";
    }
    return 0;
}

思路3:(100pts)

想出这个改进点需要理解\(dijkstra\)的计算过程

\(dijkstra\)在进行时 每次都会取出理我目前最近的一个点向其他点拓展

那么如果取出的点恰好就是那\(k\)个点中的一个 显然就没有必要继续搜了

感性证明:你继续搜的点都是由该点或距离比该点更远的点拓展得到的 最短路大小显然大于该点的最短路

所以算法可以继续优化 但用到的时间复杂度数量级是相同的

那么为什么这个算法能够\(AC\)本题呢? 给出一种感性证明:

  • case 1:假设\(k\)很大

  • 显然\(dikstra\)很容易就会遇到是那\(k\)个点中的一个而终止 \(O(knlogn)\)的复杂度会更趋近于\(O(kn)\)

  • case 2:假设\(k\)很小

  • \(O(knlogn)\)数量级会趋近于\(O(nlogn)\) 可以很稳的通过此题

综上所述 这个思想基本可以通过\(80\)%左右的数据了 而因为本题的极端数据较多 此代码可以直接\(AC\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e5+10;
struct node
{
	int dis,u;
	bool operator>(const node &a) const
	{
		return dis>a.dis;
	}
};
struct edge
{
	int v,w;
	int idx;
};
vector<edge> e[N];
int dis[N];
bool vis[N],iscity[N];
int loved[N];
int ans=LONG_LONG_MAX;
void dijkstra(int s)
{
	memset(dis,0x3f,sizeof(dis));
	dis[s]=0;
priority_queue<node,vector<node>,greater<node> >q;
	q.push({0,s});
	while(!q.empty())
	{
		int u=q.top().u;
		if(iscity[u]&&s!=u) 
		{
			ans=min(ans,dis[u]);
			break; 
		}
		q.pop();
		if(vis[u])continue;
		vis[u]=1;
		for(auto ed:e[u])
		{
			int w=ed.w,v=ed.v;
			
			if(dis[v]>dis[u]+w)
			{
				dis[v]=dis[u]+w;
				q.push({dis[v],v});
			}
			
		}
	}
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int t;
    cin>>t;
    while(t--)
    { 
        int n,m,k;
        cin>>n>>m>>k;
        for(int i=1;i<=n;++i)
        	iscity[i]=0;
        for(int i=0;i<N;++i)
       	 e[i].clear();
        for(int i=1;i<=m;++i)
        {
        	int u,v,w;
        	cin>>u>>v>>w;
        	e[u].push_back({v,w,i});
		}
		for(int i=1;i<=k;++i)
		{
			cin>>loved[i];
			iscity[loved[i]]=1;
		}
		ans=LONG_LONG_MAX;
		for(int i=1;i<=k;++i)
		{
			memset(vis,0,sizeof(vis));
			dijkstra(loved[i]);
		}	
		cout<<ans<<"\n";
    }
    return 0;
}

\(p.s.\)这个算法比较优异 实际代码跑的时间\(<1.5s\)

posted @ 2025-02-22 10:53  SamXia  阅读(8)  评论(0)    收藏  举报