图论(最短路专题):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\)