『题解』Luogu P3094 [USACO13DEC]Vacation Planning S
题目大意
有 \(n\) 个农场,用 \(1 \dots n\) 编号。有 \(m\) 道单项航线连接这些农场,每条航线从农场 \(u_i\) 连接到农场 \(v_i\),花费为 \(w_i\)。
有 \(q\) 个请求,请求从农场 \(a_i\) 到 \(b_i\),至少经过一个枢纽农场的最少花费。\(1 \dots k\) 为枢纽农场。
输出可行请求的数量及完成的总费用。
思路
由于多组请求,很容易想到 Floyd。\(1 \le n \le 200\),所以 Floyd 是可行的。
也可以用壮烈牺牲的 SPFA,跑 \(n\) 次,每次以当前农场为起始点,最后求出全源最短路。
dijkstra 也是同理。
重点是寻找经过枢纽的路径(至少这题就这个要点脑子)。
每个枢纽都遍历一遍,找出从 \(a \rightarrow i\) 加上 \(i \rightarrow b\) 的最短路径,取经过所有枢纽的最小值就行了。
具体见代码。
代码巨长,小心阅读。
代码
Floyd 版
#include <iostream>
#include <cstring>
using namespace std;
const int N=2e2+5,M=1e4+5,inf=0x3f3f3f3f;
int n,m,k,t,s=1,u,v,w,a,b,ans,cnt; // t是题目中的q,防止与队列重名
long long sum; // 会爆int
int dist[N][N]; // dist记录每两个点之间的距离
// 整个快读不过分吧?
inline int read(){
int X=0; bool flag=1; char ch=getchar();
while(ch<'0' || ch>'9'){if(ch=='-') flag=0; ch=getchar();}
while(ch>='0' && ch<='9') X=(X<<1)+(X<<3)+ch-'0',ch=getchar();
if(flag) return X;
return ~(X-1);
}
void floyd(){
for(int k=1; k<=n; k++){
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
}
}
}
}
int main(){
memset(dist,inf,sizeof(dist)); // 赋初值
n=read(),m=read(),k=read(),t=read();
for(int i=1; i<=n; i++){
dist[i][i]=0;
}
while(m--){
u=read(),v=read(),w=read();
dist[u][v]=min(dist[u][v],w); // 两点间如果之前有连边,那就取小的那个
}
floyd(); // 直接求全源最短路
while(t--){
ans=inf;
a=read(),b=read();
for(int i=1; i<=k; i++){
// 1~k中的农场都可以是枢纽,故取经过这个点的最小值
ans=min(ans,dist[a][i]+dist[i][b]);
}
if(ans==inf) continue; // 若 a 不能到 b,那就走人
cnt++; // 完成
sum+=ans; // 费用
}
cout << cnt << endl << sum << endl;
return 0;
}
SPFA 版
#include <iostream>
#include <queue>
using namespace std;
const int N=2e2+5,M=1e4+5,inf=0x3f3f3f3f;
struct edge{
int to,nxt,val; // 链式前向星存图
}e[M];
int n,m,k,t,s=1,u,v,w,a,b,ans,cnt; // t是题目中的q,防止与队列重名
long long sum; // 会爆int
int head[N],top;
int dist[N][N],vis[N]; // dist记录每两个点之间的距离,vis记录跑最短路时是否访问过
queue<int> q; // spfa用的队列
// 整个快读不过分吧?
inline int read(){
int X=0; bool flag=1; char ch=getchar();
while(ch<'0' || ch>'9'){if(ch=='-') flag=0; ch=getchar();}
while(ch>='0' && ch<='9') X=(X<<1)+(X<<3)+ch-'0',ch=getchar();
if(flag) return X;
return ~(X-1);
}
void add(int u,int v,int w){ // 加边
top++;
e[top].to=v;
e[top].val=w;
e[top].nxt=head[u];
head[u]=top;
}
void spfa(int s){ // spfa板子,s表示当前的开始顶点,start
for(int i=1; i<=n; i++){
dist[s][i]=inf;
vis[s]=0; // 由于多次使用vis,所以也要清零
}
dist[s][s]=0;
vis[s]=1;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u]; i; i=e[i].nxt){
int v=e[i].to;
if(dist[s][v]>dist[s][u]+e[i].val){
dist[s][v]=dist[s][u]+e[i].val;
if(!vis[v]){
q.push(v);
vis[v]=1;
}
}
}
}
}
int main(){
n=read(),m=read(),k=read(),t=read();
while(m--){
u=read(),v=read(),w=read();
add(u,v,w);
}
for(int i=1; i<=n; i++){ // 求出每两点之间的最短路径
spfa(i);
}
while(t--){
ans=inf;
a=read(),b=read();
for(int i=1; i<=k; i++){
// 1~k中的农场都可以是枢纽,故取经过这个点的最小值
ans=min(ans,dist[a][i]+dist[i][b]);
}
if(ans==inf) continue; // 若 a 不能到 b,那就走人
cnt++; // 完成
sum+=ans; // 费用
}
cout << cnt << endl << sum << endl;
return 0;
}
dijkstra 版
#include <iostream>
#include <queue>
using namespace std;
const int N=2e2+5,M=1e4+5,inf=0x3f3f3f3f;
struct edge{
int to,nxt,val; // 链式前向星存图
}e[M];
int n,m,k,t,s=1,u,v,w,a,b,ans,cnt; // t是题目中的q,防止与队列重名
long long sum; // 会爆int
int head[N],top;
int dist[N][N],vis[N]; // dist记录每两个点之间的距离,vis记录跑最短路时是否访问过
priority_queue<pair<int,int>> q; // dijkstra用的堆
// 整个快读不过分吧?
inline int read(){
int X=0; bool flag=1; char ch=getchar();
while(ch<'0' || ch>'9'){if(ch=='-') flag=0; ch=getchar();}
while(ch>='0' && ch<='9') X=(X<<1)+(X<<3)+ch-'0',ch=getchar();
if(flag) return X;
return ~(X-1);
}
void add(int u,int v,int w){ // 加边
top++;
e[top].to=v;
e[top].val=w;
e[top].nxt=head[u];
head[u]=top;
}
void dijkstra(int s){ // 同spfa
for(int i=1; i<=n; i++){
dist[s][i]=inf;
vis[i]=0;
}
dist[s][s]=0;
q.push(make_pair(0,s));
while(!q.empty()){
u=q.top().second;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u]; i; i=e[i].nxt){
v=e[i].to;
if(dist[s][v]>dist[s][u]+e[i].val){
dist[s][v]=dist[s][u]+e[i].val;
q.push(make_pair(-dist[s][v],v));
}
}
}
}
int main(){
n=read(),m=read(),k=read(),t=read();
while(m--){
u=read(),v=read(),w=read();
add(u,v,w);
}
for(int i=1; i<=n; i++){ // 求出每两点之间的最短路径
dijkstra(i);
}
while(t--){
ans=inf;
a=read(),b=read();
for(int i=1; i<=k; i++){
// 1~k中的农场都可以是枢纽,故取经过这个点的最小值
ans=min(ans,dist[a][i]+dist[i][b]);
}
if(ans==inf) continue; // 若 a 不能到 b,那就走人
cnt++; // 完成
sum+=ans; // 费用
}
cout << cnt << endl << sum << endl;
return 0;
}