『题解』Luogu P3720 [AHOI2017初中组]guide

题目传送门

题目大意

有一个 \(n\) 个点,\(m\) 条边的有向图,每条边有两个边权,分别对应两套 GPS 系统。

如果 John 走了路径 \(u \to v\),对于每套 GPS 系统,如果这条路为 \(u\)\(n\) 的最短路中的一条,那么这套 GPS 系统不会警告,否则警告一次。两套 GPS 系统各自独立,可以两个都不警告,也可以两个都警告。

求 John 从 \(1\) 走到 \(n\) 能收到的最小警告数

\(2 \le n \le 10^5,1 \le m \le 5 \times 10^5,1 \le \text{边权} \le 10^5.\)

思路

感觉还挺简单(

题目求的是最小警告数,那么我们就可以以走每条边会收到的警告个数为边权建一张图(以下称为“原图”),然后以 \(1\) 为起点跑最短路。

那么当下的问题就是如何确定这张图的边权。

我们可以观察到,对于每一套 GPS 系统,不会警告的路径为 \(u(u \in \{1,\dots,n\})\)\(n\) 的最短路中的所有路径,容易想到我们可以以 \(n\) 为起点跑最短路,对于 \(n \to u\) 的最短路上的边,都不会警告。但由于是有向图,就需要建立反向图再跑。

然后,我们就得到了从 \(n\) 到所有点的最短路长度。

由于 GPS 只有最短路中的边才不警告,所以我们可以将原图每条边的边权(也就是警告数)的初始值设为 \(2\),即两个 GPS 系统都会警告,然后再去减警告数。

我们遍历所有边,由于是有向图,所以在链式前向星中,\(3\) 张图中每条边的编号都相同,所以可以直接取每条边的属性。

设当前这条边在 GPS 图中起点为 \(u\),终点为 \(v\)

我们可以直接通过检测两个 GPS 图中 \(dist_v+val\) 是否等于 \(dist_u\)(其中 \(dist_i\) 表示 \(n\)\(i\) 的最短路径,\(val\) 分别为两张图中的边权),若等于,则说明该边为这张 GPS 图中的一条最短路经过的边,是不会警告的,将原图中该边边权减一(两张图需要分别判)。

这样就能得到一个完整的原图了,跑一遍原图起点为 \(1\) 的最短路就可以 A 啦!

代码

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
template<typename T=int>
inline T read(){
    T 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^48),ch=getchar();
    if(flag) return X;
    return ~(X-1);
}

const int N=1e6+5,M=5e6+5,inf=0x3f3f3f3f;
/* 对于 3 张图:
   - 编号为 1~3,其中 1 为原图,2~3 分别为两张 GPS 图
   - 数组的第一个下标表示图的编号,例如 e[1][x] 为原图中某条边 */
struct edge{
    int from,to,nxt,val;
}e[4][M<<1]; // 链式前向星
int n,m,u,v,w1,w2;
int head[4][N],top[4]; // 链式前向星
int dist[4][N],vis[4][N]; // 跑dij用
priority_queue<pair<int,int>> q; // 同上

/* add 加边
   u: 起点
   v: 终点
   w: 边权
   x: 图的编号
   top: 当前边的编号 */
void add(int u,int v,int w,int x,int top){
    e[x][top].from=u;
    e[x][top].to=v;
    e[x][top].val=w;
    e[x][top].nxt=head[x][u];
    head[x][u]=top;
}

/* dijkstra 最短路
   s: 起点
   x: 图的编号 */
void dijkstra(int s,int x){
    // P.S. 初始化在 main
    dist[x][s]=0;
    q.push({0,s});
    while(!q.empty()){
        int u=q.top().second;
        q.pop();
        if(vis[x][u]) continue;
        vis[x][u]=1;
        for(int i=head[x][u]; i; i=e[x][i].nxt){
            int v=e[x][i].to;
            if(dist[x][v]>dist[x][u]+e[x][i].val){
                dist[x][v]=dist[x][u]+e[x][i].val;
                q.push({-dist[x][v],v});
            }
        }
    }
}

int main(){
    n=read(),m=read();
    for(int i=1; i<=m; i++){
        u=read(),v=read(),w1=read(),w2=read();
        add(u,v,2,1,i); // 原图
        add(v,u,w1,2,i); // 两张反向图
        add(v,u,w2,3,i);
    }
    // 初始化dist
    memset(dist,0x3f,sizeof(dist));
    dijkstra(n,2); // 反向跑两次
    dijkstra(n,3);
    for(int i=1; i<=m; i++){
        int u=e[2][i].from,v=e[2][i].to;
        if(dist[2][v]+e[2][i].val==dist[2][u]) e[1][i].val--;
        if(dist[3][v]+e[3][i].val==dist[3][u]) e[1][i].val--;
    }
    dijkstra(1,1); // 按处理好的边权跑原图
    printf("%d\n",dist[1][n]);
    return 0;
}
posted @ 2022-12-25 18:02  仙山有茗  阅读(20)  评论(0编辑  收藏  举报