题意:
规定起点为1
号点,主角要求从起点出发,必须要经过其它5
个点(这5
个点的编号任意),且这 5
个点的 经过顺序不唯一,问 主角总行程的最小值 是多少?
图中的节点当然不仅仅只是上面所说的6
个点,注意上的所有节点都是联通的,而且节点之间的边是无向边。
思路:
拿到一个题目并思考运用什么算法的时候,我们首先最要关心的是时间复杂度。
我们先想想暴力怎么做,我们可以 先爆搜枚举所有摆放的顺序,一共有 5
个需要经过的节点,因此就会有 5!
种拜访的顺序。
(由于起点1
号点是固定的,我们dfs
爆搜顺序实际上是搜索2~6
这后面5
个数的全排列)
加入我们已经得到了一种拜访的顺序:1-->2-->5-->4-->3-->6
,很显然,如果想要 总行程最短,当然 每从一个点走到下一个点的时候 按照 最短路的策略 是最优的,也就是说,我们只需要求一下从1–>2的最短路,从2–>5的最短路…即可。
至此,我们将顺序确定以后就将问题转化成了单源最短路问题,这个单源最短路径我们可以用 堆优化dijkstra 来做。
如果我们 每次都要求最短路的话,那么我们需要求 5 * 5!
次dijkstra
,也就是 600 * mlogn
,看一眼数据范围,这样肯定会超时。
接下来我们想一下怎么优化,我们可以这样想:
我们可以先将6
个站点中以 每个站点为起点 到 其它5
个站点 的 最短路 预处理一下,将答案存储到一个 二维数组ans[7][7]
中,ans
数组 第一维下标 表示 起点的 映射值,第二维下标 表示 终点的 映射值,则ans[i][j]
表示从 站点i
到 站点j
的最短距离。
(我们将除起点1
之外的5
个站点编号 映射 成了2~6
之间的数,用hah
数组实现 映射)
之后我们则可以用 dfs
枚举每一种顺序,例如我们枚举到了一个顺序:1-->2-->5-->4-->3-->6
,那么对于 相邻两节点之间的最短路 我们 查表 即可,无需再次进行求最短路的操作。
之后将 5
个间隔的最短路相加,我们就得到了一个答案,但这可能还并不是最小值,我们还需要用一个 全局变量res
(注意,一定是全局变量)在 dfs
中 迭代更新出最小的答案。
时间复杂度:
6 * 2 * mlogn (跑6
次 无向图堆优化dijkstra 打表) + 5!
(dfs求全排列,可忽略不计)
n<=5e4
,m<=1e5
,算出来大概是2e7
,在合法范围内。
代码:
得到正确思路后马上开始敲,debug大法 “printf(“===”); exit(0);” 牛掰
#include<bits/stdc++.h>
using namespace std;
int n, m;
const int N = 5e4+10, M = 2e5+10;
#define inf 0x3f3f3f3f
typedef pair<int, int> pii;
#define x first
#define y second
int sta[7];//存放 起点及5个亲戚站点 的编号
int ans[7][7];//存答案,上方有解释
int dist[N], h[M], e[M], ne[M], w[M], idx;
bool st[N], ST[7];
int hah[N];//存放 起点及5个亲戚站点 的映射值
int res = inf;
void add(int a, int b, int c){
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
void dijk_init(int sta)
{
memset(dist, 0x3f, sizeof dist);
memset(st, false, sizeof st);
dist[sta] = 0;
priority_queue<pii, vector<pii>, greater<pii>> pq;
pq.push({dist[sta], sta});
while(pq.size())
{
pii tmp = pq.top();
pq.pop();
int ver = tmp.y, distance = tmp.x;
for(int i=h[ver]; ~i; i=ne[i])
{
int j = e[i];
if(dist[j]>distance+w[i])
{
dist[j] = distance + w[i];
pq.push({dist[j], j});
}
}
}
for(int i=1;i<=n;++i)
{
if(!hah[i]) continue;//如果不是亲戚站点或起点则跳过,不存答案
ans[hah[sta]][hah[i]] = dist[i];
}
}
void dfs(int u, int a[])
{
if(u==7)
{
int tmp = 0;
for(int i=1;i<=5;++i)
{
tmp+=ans[a[i]][a[i+1]];//tmp为某一顺序得到的答案
}
res = min(res, tmp);//迭代答案,更新最小值
return ;
}
for(int i=2;i<=6;++i)
{
if(!ST[i])
{
ST[i] = true;
a[u] = i;
dfs(u+1, a);
ST[i] = false;
}
}
}
int main()
{
scanf("%d%d", &n, &m);
//存放6个站点编号及其它们的映射,两者互为对偶
sta[1] = 1, hah[sta[1]] = 1;
for(int i=2;i<=6;++i) scanf("%d", &sta[i]), hah[sta[i]] = i;
memset(h, -1, sizeof h);
for(int i=0;i<m;++i)
{
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
add(a, b, w), add(b, a, w);
}
for(int i=1;i<=6;++i) dijk_init(sta[i]);//跑6次dijkstra求出以每个站点为起点到其它5个站点的最短路
int a[10];
a[1] = 1;//起点是固定的1号点
ST[1] = true;
dfs(2, a);//从第2个站点开始搜,a数组存放排列顺序。
cout<<res<<endl;
return 0;
}