城市平乱
时间限制:1000 ms | 内存限制:65535 KB
难度:4
描述
南将军统领着N个部队,这N个部队分别驻扎在N个不同的城市。
他在用这N个部队维护着M个城市的治安,这M个城市分别编号从1到M。
现在,小工军师告诉南将军,第K号城市发生了暴乱,南将军从各个部队都派遣了一个分队沿最近路去往暴乱城市平乱。
现在已知在任意两个城市之间的路行军所需的时间,你作为南将军麾下最厉害的程序员,请你编写一个程序来告诉南将军第一个分队到达叛乱城市所需的时间。
注意,两个城市之间可能不只一条路。
输入
第一行输入一个整数T,表示测试数据的组数。(T<20)
每组测试数据的第一行是四个整数N,M,P,Q(1<=N<=100,N<=M<=1000,M-1<=P<=100000)其中N表示部队数,M表示城市数,P表示城市之间的路的条数,Q表示发生暴乱的城市编号。
随后的一行是N个整数,表示部队所在城市的编号。
再之后的P行,每行有三个正整数,a,b,t(1<=a,b<=M,1<=t<=100),表示a,b之间的路如果行军需要用时为t
数据保证暴乱的城市是可达的。
输出
对于每组测试数据,输出第一支部队到达叛乱城市时的时间。每组输出占一行
样例输入
1
3 8 9 8
1 2 3
1 2 1
2 3 2
1 4 2
2 5 3
3 6 2
4 7 1
5 7 3
5 8 2
6 8 2
样例输出
4
这是一道简单的图论题,只要逆向思考一下,从暴乱点找到各个点之间的距离,然后再从部队驻扎点中找个最短的距离即可,由于该图是一个点比较少的稀疏图。故简单的Dijkstra即可过,我试了一下队列优化的Bellman-Ford,结果也过了,而且耗时更短,可能是数据比较水吧,两种代码都贴在了下面
Dijkstra:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<queue>
#define MAX_N 110
#define MAX_V 1010
#define MAX_E 100010
using namespace std;
int cost[MAX_V][MAX_V],dis[MAX_V];
bool used[MAX_V];
const int INF = 99999999;
int N,V,E,Q,num[MAX_N];
void Dijkstra(int a){
fill(dis+1,dis+1+V,INF);
fill(used+1,used+1+V,false);
dis[a] = 0;
while(true){
int v = -1;
for(int u=1;u<=V;u++){
/*因为每个点只能用来松弛一次,故用used来标记一下。*/
if(!used[u] && (v == -1 || dis[u] < dis[v])) v = u;
}
if(v == -1) break;//即所有的点都被使用过。
used[v] = true;
for(int u=1;u<=V;u++){
dis[u] = min(dis[u],dis[v]+cost[v][u]);//也可用if来代替。
}
}
}
void input(){
scanf("%d %d %d %d",&N,&V,&E,&Q);
for(int i=1;i<=V;i++)//对图进行初始化
for(int j=1;j<=V;j++)
if(i == j) cost[i][j] = 0;
else cost[i][j] = INF;
for(int i=1;i<=N;i++)
scanf("%d",&num[i]);
int a,b,c;
for(int i=1;i<=E;i++){
scanf("%d %d %d",&a,&b,&c);
if(cost[a][b] > c){//这里主要注意,因为题目说两点之间可能存在多条道路,故保存权值最小的那条路
cost[a][b] = c;
cost[b][a] = c;
}
}
}
int main(void){
int t;
scanf("%d",&t);
while(t--){
input();
Dijkstra(Q);
/*本题可以到这来理解,即从暴乱点开始搜索,得到暴乱点到各个点的最短距离
即,在从部队驻扎地找最下的距离,即可。*/
int min = 0x7fffffff;
for(int i=1;i<=N;i++){
if(i != Q && dis[num[i]] < min)
min = dis[num[i]];
}
printf("%d\n",min);
}
return 0;
}
队列优化的Bellman-ford算法具体实现可以参考一些其他的文献。这里我只把这个贴的思路写在注释里了,完整代码如下:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
#include<cstdlib>
#define MAX_N 110
#define MAX_V 1010
#define MAX_E 200010//因为是数组模拟邻接表,故每条边要存两次
using namespace std;
const int INF = 999999999;
int first[MAX_E],next[MAX_E];
int u[MAX_E],v[MAX_E],w[MAX_E],dis[MAX_V];//用的而是数组来模拟邻接表存图
int N,V,E,Q,num[MAX_N];
bool book[MAX_V];
void Bellman(int a){
fill(dis+1,dis+1+V,INF);
dis[a] = 0;
queue<int>q;
q.push(a);
book[a] = 1;
while(!q.empty()){
int k = first[q.front()];
while(k!=-1){
if(dis[v[k]] > dis[u[k]] + w[k]){
dis[v[k]] = dis[u[k]] + w[k];
if(book[v[k]] == 0){//保证队列中这个点只有一个。
book[v[k]] = 1;
q.push(v[k]);
}
}
k = next[k];
}
book[q.front()] = 0;
q.pop();
}
}
void input(){
scanf("%d %d %d %d",&N,&V,&E,&Q);
memset(first,-1,sizeof(first));
memset(book,0,sizeof(book));
for(int i=1;i<=N;i++){
scanf("%d",&num[i]);
}
int j = 0;
int a,b,c;
for(int i=1;i<=E;i++){
scanf("%d %d %d",&a,&b,&c);
j++;
u[j] = a,v[j] = b,w[j] = c;
next[j] = first[u[j]];//可以理解为next就是连接到上一条边的位置,输出时边也是逆序的
first[u[j]] = j;
//由于是无向图,故要把边存两次,这也是数组模拟的弊端。
j++;
u[j] = b,v[j] = a,w[j] = c;
next[j] = first[u[j]];
first[u[j]] = j;
}
}
int main(void)
{
int t;
scanf("%d",&t);
while(t--){
input();
Bellman(Q);
int min = 0x7fffffff;
for(int i=1;i<=N;i++){
if(dis[num[i]] < min)
min = dis[num[i]];
}
printf("%d\n",min);
}
return 0;
}