P6464 [传智杯 #2 决赛] 传送门

124 阅读3分钟

P6464 [传智杯 #2 决赛] 传送门

这题的话其实也是一个多源最短路的问题,所以可以使用floyd算法来求最短路,做洛谷题单到目前为止感觉floyd使用频率很高啊。

这题有一个需要注意的点,也是本题的难点,就是要优化floyd算法,不然的话会tle最后三个点,本题的朴素做法就是用一个for的双重循环枚举每次传送门的位置,然后每次都调用一遍floyd来更新最短路,但是这样的时间复杂度是O(n^5),对于本体的数据100而言是不能接受的,所以要想办法优化一下。

很容易就能想到每次其实只要重新计算与建传送门两点有关联的点即可。

所以这题考察的其实是对floyd循环意义的理解,从而改进floyd在本题中的使用。

floyd中的g(k , i , j)我理解的实际意义是在经过的点编号不超过k的情况下从i到j的最短路径,换种说法就是当中间节点是1到k之间的点时,从i到j的最短路。

这里再说明一下floyd算法吧,floyd中的核心公式

f(i , j , k) = min(f(i , j , k - 1) , f(i , k , k - 1) + f(k , j , k - 1))

其中前者的意思是从经过从1到k - 1的点从i到j的最短路(不过k),后者是经过从1到k - 1的点从i到k,再通过1到k - 1的点从k到j的最短路(经过k),所以经过编号1到k的点从i到j的最短路就是这两种(经过k与没经过k)之间的最小值。

所以这题就只需要每次计算经过传送门两点的点的最小值就行了。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef pair <ll , ll> pii;
typedef priority_queue <ll ,vector<ll> ,greater<ll> > xiao;  
typedef priority_queue <ll ,vector<ll> ,less<ll> > da; 
const int N=1e5   + 10,M = 0x3f3f3f3f;
const ull P = 131;

int n,m;
int g[105][105],d[105][105];

void floyd()
{
	for(int k = 1 ; k <= n ; k++)
	{
		for(int i = 1 ; i <= n ; i++)
		{
			for(int j = 1 ; j <= n ; j++)
			{
				g[i][j] = min(g[i][j] , g[k][j] + g[i][k]);
			}
		}
	}
}

int main()
{
	std::ios::sync_with_stdio(false);
    std::cin.tie(0),cout.tie(0);

	cin>>n>>m;
	for(int i = 1 ; i <= n ; i++)
	{
		for(int k = 1 ; k <= n ; k++)
		{
			if(i == k)
			{
				g[i][k] = 0;
			}else
			{
				g[i][k] = M;
			}
		}
	}                   //初始化 
	
	while(m--)
	{
		int a,b,c;
		cin>>a>>b>>c;
		g[a][b] = c;
		g[b][a] = c;
	}
	
	floyd();
	memcpy(d , g , sizeof g);       //需要一个备用数组记录每一次的初始数据,之后floyd并且计算完sum后方便还原初始值 
	
	ll minn = M,sum = 0;
	for(int i = 1 ; i < n ; i++)
	{
		for(int j = i + 1 ; j <= n ; j++)      //for循环每一轮的传送门的位置 
		{
			g[i][j] = 0;
			g[j][i] = 0;
			
			for(int k = 1 ; k <= n ; k++)
			{
				for(int p = 1 ; p <= n ; p++)
				{
					g[k][p] = min(g[k][p] , g[k][i] + g[i][p]);    //计算从k到p中需要经过编号为i点的数据 
				}
			}

			for(int k = 1 ; k <= n ; k++)
			{
				for(int p = 1 ; p <= n ; p++)
				{
					g[k][p] = min(g[k][p] , g[k][j] + g[j][p]);   //计算囧过编号为j的数据 
				}
			}
			
			for(int k = 1 ; k <= n ; k++)
			{
				for(int p = 1 ; p < k ; p++)
				{
					sum += g[k][p];
				}
			} 

			minn = min(minn , sum);
			sum = 0;
			memcpy(g , d , sizeof d);      //还原 
		}
	}
	
	cout<<minn;
}