Sightseeing Trip(又双叒被大佬们踩爆了)
前言
- 本来早就想写的,但是由于我太菜,一直没有提前做完当天的题,就磨到今天了。。。
- 。。。我好蒻啊,当我做出时又是十几位大佬做出来了。
题目
分析
(后文中的dis[i][j]表示i到j的最短路,w[i][j]表示i->j的边权)
怎么构出一个环呢???
1.
我们很容易就能想到一个做法:answer = min(dis[i][j] + w[j][i]), i != j
可是我们很快就会发现一个事情:
而这种做法并不能保证有三个点,可能会出现下面的路线
1 -> 2 -> 1
那我们就将经过了1条路的最短路(w[i][j])保存一份,将经过了不止1条路的最短路也保存一份(dis[i][j])。
可是我们又会发现:dis[i][j]和dis,w都有关系,dp式,初始化,输出路径,比较答案都会十分麻烦。
2.
给这n个点编号:1,2,3…(n - 1), n
我们会发现:一个环,Ta绝对有一个编号最大的节点(k)
用此契机,我们可不可以这样想呢:
算出与k相连的两个点(i,j)的最短路,answer = Min(dis[i][j] + w[i][k] + w[k][j]);
注意:dis[i][j]的中转点的编号一定<k(因为k是这个环中的编号最大的点)
#include <vector>
#include <iostream>
#define LL long long
using namespace std;
const int MAXN = 105;
const LL INF = 0x7f7f7f7f;
int n, m;
int fa[MAXN][MAXN];//输出路径
LL ans = INF;//答案
LL g[MAXN][MAXN];//保存边权
LL dis[MAXN][MAXN];//dis[i][j]表示i -> j的最短路
vector<int> cnt;//保存路径
void inout(int, int);//将新路径保存在cnt里
LL Min(LL x, LL y) {return x < y ? x : y;}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
dis[i][j] = INF;
g[i][j] = INF;
}
dis[i][i] = 0;
g[i][i] = 0;
}//初始化
for (int i = 1; i <= m; i++) {
int l, r;
LL val;
cin >> l >> r >> val;
g[l][r] = g[r][l] = dis[l][r] = dis[r][l] = Min(g[l][r], val);
fa[l][r] = l;
fa[r][l] = r;
}//输入
for (int k = 1; k <= n; k++) {//环中编号最大的点为k
for (int i = 1; i < k; i++)
for (int j = i + 1; j < k; j++)//i,j都不能大于k
if (dis[i][j] + g[j][k] + g[k][i] < ans) {//这个环比目前的最优解更短
ans = dis[i][j] + g[j][k] + g[k][i];//更新答案
cnt.clear();//别忘了清空
inout(i, j);//保存新路径
cnt.push_back(k);//别忘了k也是环里的一个节点
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (dis[i][k] + dis[k][j] < dis[i][j]) {
fa[i][j] = fa[k][j];
dis[i][j] = dis[i][k] + dis[k][j];
}
}
}//floyd算出最短路(中转点没有用到大于k的点)
}
if(ans == INF) {//没有环
cout << "No solution.";
}
else {
for (vector<int>::iterator it = cnt.begin(); it != cnt.end(); it++) {//输出路径
cout << *it << " ";
}
}
return 0;
}
void inout(int l, int r) {//floyd输出路径经典模板
if(l == r) {
cnt.push_back(l);
return;
}
inout(l, fa[l][r]);
cnt.push_back(r);
return;
}
可能会想到的错误剪枝(可以跳过)
有人会觉得53~60行还可以剪枝 (或者只有我傻傻的这样想?),可以改成下面这段代码
for (int i = 1; i <= k; i++) {
for (int j = 1; j <= k; j++) {
if (dis[i][k] + dis[k][j] < dis[i][j]) {
fa[i][j] = fa[k][j];
dis[i][j] = dis[i][k] + dis[k][j];
}
}
}
Ta们想到这样剪枝的原因是这样的:i -> j的最短路中是不能经过大于等于k的节点的,所以可以剪掉后面的循环(因为后面的循环中i,j是大于k的,所以肯定是用不上的)
取等分析 :下一次循环k++后,i -> j的最短路中是不能有节点经过大于k(上一次循环的k),所以要取等号。
可是我们会发现这样做连样例都过不了。
GM的数据有问题
NO!NO!NO!,这个“锅”与郭老师无关
按照这样的做法,dis[i][j]可以用的中转点只有大于等于i,j,且小于等于k的点了(手算一下就可知),可是我们可以用小于i,j的点啊!所以这个做法是错的。 (还不清楚的私信我呗)