『题解』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;
}
posted @ 2022-01-21 21:18  仙山有茗  阅读(34)  评论(0编辑  收藏  举报