题意:牛牛接力赛,共有 N (2 ≤ N ≤ 1,000,000) 头牛,T (2 ≤ T ≤ 100) 条连接两个路口的无向边,每个路口可以有多头牛,给定起点 s 和终点 e ,问每头牛都只跑一条无向边的最短路长度是多少?
思路:先计算分别从起点和终点到各点经过k ( 0 <= k < 节点数 ) 的边的最短路径,然后枚举重复的相邻边。取最小值
简单证明:虽然枚举重复边的时候剩余的边数一定要是奇数,可能有时候并不能在最短的边上来回走然后退出,可能要走另外一个奇数的环,但是我们在计算dist时 k 最大取到 节点数 - 1 ,这就保证了有环的话一定已经考虑进去了。
#pragma warning (disable:4786)
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
#define nMAX 205
#define INF 0x3fffffff
/** 牛的头数,虽然题目给的值范围有点吓人,但是根据路线条数 <= 100 可知牛的数量一定 <= 200 */
int n;
/* dist[0][i][j] = x 表示从起点到i 经过j 条边的最短路为x
* dist[1][i][j] = x 表示从终点到i 经过j 条边的最短路为x
*/
int dist[2][nMAX][nMAX];
/** code[i] = j 表示原始编号为i 的路口重新规整后的编码为j ( 1 <= j <= 200 ) */
int code[1005];
/** 规整后的编码号最大值 */
int id;
/** 相邻边节点,用于枚举重复的边 */
struct Node{
int v1; //路线的起点
int v2; //路线的终点
int dis; //路线的长度
};
/** 存储所有相邻的边,为最小优先队列*/
priority_queue<Node> queue1;
bool operator < ( Node node1, Node node2 ){
return node1.dis > node2.dis;
}
/** 邻接表存储 */
vector<Node> edge[nMAX];
/* 计算dist数组
* @name tag = 0 或 1 其中为0 表示s 是起点,为1 表示s 是终点
* @name id 规整后的编码号最大值
* @name s 起点或终点,取决于tag 的值
*/
void calc(int tag, int id, int s )
{
int i,j;
/** 初始化 */
for(i=0;i <= id;i++)
for(j=0;j<=id;j++)
dist[tag][i][j]=INF;
dist[tag][s][0]=0;
/** step 表示距离s 的路线数 */
int step;
for( step = 1; step < id; step ++ )
{
for( i = 1; i <= id; i++ )
{
if( dist[tag][i][step-1] == INF )
continue;
for( int k = 0; k < edge[i].size(); k ++ ){
Node tmp = edge[i][k];
j = tmp.v2;
if( dist[tag][j][step] > dist[tag][i][step-1] + tmp.dis ){
dist[tag][j][step] = dist[tag][i][step-1] + tmp.dis;
}
}
}
}
}
int main(){
int i, j;
/** 边数 */
int t;
/** 起点 */
int s;
/** 终点 */
int e;
memset( code, 0, sizeof(code) );
scanf ("%d%d%d%d",&n, &t, &s, &e );
while( !queue1.empty() )
queue1.pop();
//for( i = 1; i <= 2 * t; i ++ ){
// edge[i].clear();
//}
id = 1;
for( i = 1; i <= t; i ++ ){
int a, b, d;
scanf( "%d%d%d", &d, &a, &b );
/** 对路口编号进行规整,使之在区间 1—200 */
if( code[a] == 0 ){
code[a] = id;
id ++;
}
if( code[b] == 0 ){
code[b] = id;
id ++;
}
a = code[a];
b = code[b];
/** 对邻接表进行更新 */
Node e;
e.v2 = b; e.dis = d;
edge[a].push_back(e);
e.v2 = a; e.dis = d;
edge[b].push_back(e);
/** 相邻边存储在queue 中,用于之后的枚举 */
Node node;
node.v1 = a;
node.v2 = b;
node.dis = d;
queue1.push(node);
}
id--;
s = code[s]; //忘了此处wa
e = code[e];
/** 计算从起点到各个节点经历k (0 <= k <= id ) 条边的最短路 */
calc( 0, id, s );
/** 计算从终点到各个节点经历k (0 <= k <= id ) 条边的最短路 */
calc( 1, id, e );
/** 对每条相邻边进行枚举,因为最后的答案一定是在较小的边上重复多次,然后退出的 */
int min = INF;
while( !queue1.empty() ){
Node node;
node = queue1.top();
queue1.pop();
int x, y;
for( i = 0; i <= id; i ++ ){
/** 注意两头可以倒置, 故有两种情况 */
/** 情况一 */
if( dist[0][node.v1][i] != INF ){
for( j = 0; j <= id && j <= n - i; j ++ ){
if( dist[1][node.v2][j] != INF ){
x = n - i - j;
if( x & 1 ){ //一定要为奇数
y = dist[0][node.v1][i] + dist[1][node.v2][j] + x * node.dis;
if( min > y )
min = y;
}
}
}
}
/** 情况二 */
if( dist[0][node.v2][i] != INF ){
for( j = 0; j <= id && j <= n - i; j ++ ){
if( dist[1][node.v1][j] != INF ){
x = n - i - j;
if( x & 1 ){
y = dist[0][node.v2][i] + dist[1][node.v1][j] + x * node.dis;
}
if( min > y )
min = y;
}
}
}
}
}
printf("%d\n", min);
return 0;
}