『题解』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;
}