【洛谷2505】[HAOI2012] 道路(最短路图)
- 给定一张\(n\)个点\(m\)条边的有向图,对每条边求出有多少条不同的最短路包含它(相同两点间不同的最短路计算多次)。
- \(n\le1.5\times10^3,m\le5\times10^3\)
最短路图
考虑\(n\)这么小,我们可以直接枚举起点\(s\),\(Dijkstra\)一遍对所有点求出\(dis_x\)表示\(s\)到\(x\)的最短路。
那么,一条边\((u,v,w)\)能出现在从\(s\)出发的最短路中,当且仅当\(dis_u+w=dis_v\)。
由于\(dis_u+w=dis_v\)的必要条件是\(dis_u<dis_v\),因此有用的边实际上构成一张\(DAG\)。(这可以看作是最短路树的进阶版本,不妨称之为最短路图。)
容易发现,最短路图中的任意一条从\(s\)出发的路径都是一条最短路,且所有从\(s\)出发的最短路都被包含在了这张最短路图之中。
所以我们只要拓扑排序一下,然后求出\(f_u\)表示从\(s\)到\(u\)的路径条数,\(g_v\)表示从\(v\)到之后任意点的总路径条数。
那么从\(s\)出发的最短路对于边\((u,v,w)\)的贡献就是\(f_u\times g_v\)。
代码:\(O(n^2logn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 1500
#define M 5000
#define X 1000000007
#define add(x,y,z) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].v=z)
using namespace std;
int n,m,ee,lnk[N+5],ans[M+5];struct edge {int to,nxt,v;}e[M+5];
typedef pair<int,int> Pr;priority_queue<Pr,vector<Pr>,greater<Pr> > Q;
int dis[N+5],vis[N+5];I void Dij(CI s)//最短路
{
RI i,k;for(i=1;i<=n;++i) dis[i]=1e9,vis[i]=0;Q.push(make_pair(dis[s]=0,s));
W(!Q.empty()) if(k=Q.top().second,Q.pop(),!vis[k]) for(i=lnk[k];i;i=e[i].nxt)
dis[k]+e[i].v<dis[e[i].to]&&(Q.push(make_pair(dis[e[i].to]=dis[k]+e[i].v,e[i].to)),0);
}
int deg[N+5],q[N+5],f[N+5],g[N+5];I void Solve(CI s)//拓扑求解
{
RI i,j,k,H,T;for(k=1;k<=n;++k) for(i=lnk[k];i;i=e[i].nxt) dis[k]+e[i].v==dis[e[i].to]&&++deg[e[i].to];//建立最短路图
for(i=1;i<=n;++i) f[i]=0,g[i]=1;f[q[H=T=1]=s]=1;//初始化f,g,一开始队列中只有起点s
W(H<=T) for(i=lnk[k=q[H++]];i;i=e[i].nxt)//拓扑排序
dis[k]+e[i].v==dis[e[i].to]&&(f[e[i].to]=(f[e[i].to]+f[k])%X,!--deg[e[i].to]&&(q[++T]=e[i].to));//转移f
W(T) for(i=lnk[k=q[T--]];i;i=e[i].nxt)
dis[k]+e[i].v==dis[e[i].to]&&(g[k]=(g[k]+g[e[i].to])%X,ans[i]=(ans[i]+1LL*f[k]*g[e[i].to])%X);//转移g;更新每条边的答案
}
int main()
{
RI i,x,y,z;for(scanf("%d%d",&n,&m),i=1;i<=m;++i) scanf("%d%d%d",&x,&y,&z),add(x,y,z);
for(i=1;i<=n;++i) Dij(i),Solve(i);for(i=1;i<=m;++i) printf("%d\n",ans[i]);return 0;//枚举起点
}
待到再迷茫时回头望,所有脚印会发出光芒