Sightseeing Trip(又双叒被大佬们踩爆了)

前言

  1. 本来早就想写的,但是由于我太菜,一直没有提前做完当天的题,就磨到今天了。。。
  2. 。。。我好蒻啊,当我做出时又是十几位大佬做出来了。

题目

分析

(后文中的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的点啊!所以这个做法是错的。 (还不清楚的私信我呗)

posted @ 2020-08-02 15:22  C2022lihan  阅读(48)  评论(0编辑  收藏  举报