Description
给定一个 n n n 个点, m m m 条边的无向图,两个人分别从 S S S 走到 T T T、 T T T 走到 S S S。
每条边需要花费一定的时间通过。
两个人走的路径耗时都是最短的,且两人不会相遇(即在同一时刻两人不在同一点或同一边)。
问有多少种方案满足上述要求。
两种方案不同当且仅当有至少一人走的路径不同。
Solution
首先考虑正向得出答案。
我们发现一条边 ( u , v ) (u,v) (u,v) 若能是 S S S 到 T T T 的最短路的一部分,那么 d i s ( S , u ) dis(S,u) dis(S,u) 和 d i s ( v , T ) dis(v,T) dis(v,T) 一定确定,即使是在两条不一样的最短路上。
那么为了不相遇,两人走的路径包含的边集一定不交。
然后就会顺畅的想到每条边松弛后只更新一条边,保证最后得出的每一条最短路的边集的交集为空。
然而稍微细想就会发现这是无法保证的,同时也不是正确答案。
因为:
- 无法保证松弛后更新的唯一边在最短路中。
- 可能 a , b a,b a,b 两条最短路径有交但 ( a , c ) , ( c , a ) , ( b , c ) , ( c , b ) (a,c),(c,a),(b,c),(c,b) (a,c),(c,a),(b,c),(c,b) 是合法方案。
- 两条最短路径可能只在一个点相交,而不是边。
说了那么多,就是为了让你死了正向推这条心(也有可能作者太菜想不出来) 发现容斥是可行的。
Realsol
发现两个人最多只会相遇一次(注意两条最短路径可能有多个部分重叠)。
分类讨论相遇是在点或边。
设 M d i s Mdis Mdis 为最短路, d i s s 0 , x , c n t t 0 , x , d i s s 1 , x , c n t t 1 , x diss_{0,x},cntt_{0,x},diss_{1,x},cntt_{1,x} diss0,x,cntt0,x,diss1,x,cntt1,x 分别为 S S S 到 x x x 的最短耗时、最短路个数, T T T 到 x x x 的最短耗时、最短路个数, d i s ( x , y ) dis(x,y) dis(x,y) 代表无向边 ( x , y ) (x,y) (x,y) 的耗时。
若相遇在点 x x x,则满足 d i s s 0 , x = d i s s 1 , x diss_{0,x}=diss_{1,x} diss0,x=diss1,x,同时满足 d i s s 0 , x + d i s s 1 , x = M d i s diss_{0,x}+diss_{1,x}=Mdis diss0,x+diss1,x=Mdis 的最短耗时要求,此方案个数为 ( c n t t 0 , x × c n t t 1 , x ) 2 (cntt_{0,x}\times cntt_{1,x})^2 (cntt0,x×cntt1,x)2。
若相遇在边 ( x , y ) (x,y) (x,y),则满足 d i s s 0 , x + d i s ( x , y ) > d i s s 1 , y , d i s s 1 , y + d i s ( x , y ) > d i s s 0 , x diss_{0,x}+dis(x,y)>diss_{1,y},diss_{1,y}+dis(x,y)>diss_{0,x} diss0,x+dis(x,y)>diss1,y,diss1,y+dis(x,y)>diss0,x,同时满足 d i s s 0 , x + d i s ( x , y ) + d i s s 1 , y = M d i s diss_{0,x}+dis(x,y)+diss_{1,y}=Mdis diss0,x+dis(x,y)+diss1,y=Mdis 的最短耗时要求,此方案个数为 ( c n t t 0 , x × c n t t 1 , y ) 2 (cntt_{0,x}\times cntt_{1,y})^2 (cntt0,x×cntt1,y)2。
注意此为 S → x → y → T S\rightarrow x\rightarrow y\rightarrow T S→x→y→T 的情况,由于 ( x , y ) (x,y) (x,y) 是无向边,所以还要考虑 S → y → x → T S\rightarrow y\rightarrow x\rightarrow T S→y→x→T 的情况。
最后用总方案数 c n t t 0 , T × c n t t 1 , S cntt_{0,T}\times cntt_{1,S} cntt0,T×cntt1,S 减去上面不满足要求的方案即可得出正确答案。
Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int mo=1000000007;
priority_queue<pair<ll,int> >q;
int n,m,s,t;
int tot,head[100010];
int u[200020],v[200020];
ll diss[2][100010],dis[100010],d[200020];
ll cntt[2][100010],cnt[100010];
bool vis[100010];
struct edge{
int to,next;
ll w;
}e[400040];
void add(int x,int y,ll z){
e[++tot]=edge{y,head[x],z};
head[x]=tot;
}
void dijsktra(ll *dis,ll *cnt,int st){
for(int i=1;i<=n;i++){
dis[i]=1e15;
vis[i]=0;
}
dis[st]=0,cnt[st]=1;
q.push({0,st});
while(q.size()){
int x=q.top().second;
q.pop();
if(vis[x]) continue;
vis[x]=1;
for(int i=head[x];i;i=e[i].next){
int y=e[i].to;
ll w=e[i].w;
if(dis[y]<dis[x]+w) continue;
else if(dis[y]==dis[x]+w){
cnt[y]+=cnt[x];
cnt[y]%=mo;
continue;
}
cnt[y]=cnt[x];
dis[y]=dis[x]+w;
if(vis[y]) continue;
q.push({-dis[y],y});
}
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(nullptr);
cin>>n>>m>>s>>t;
for(int i=1;i<=m;i++){
int x,y;
ll z;
cin>>x>>y>>z;
add(x,y,z),add(y,x,z);
u[i]=x,v[i]=y,d[i]=z;
}
dijsktra(diss[0],cntt[0],s);
dijsktra(diss[1],cntt[1],t);
ll ans=cntt[0][t]*cntt[0][t]%mo;
for(int i=1;i<=n;i++){
if(diss[0][i]==diss[1][i]&&diss[0][i]+diss[1][i]==diss[0][t]){
ans=(ans+mo-cntt[0][i]*cntt[1][i]%mo*cntt[0][i]%mo*cntt[1][i]%mo)%mo;
}
}
for(int i=1;i<=m;i++){
if(diss[0][u[i]]+d[i]>diss[1][v[i]]&&diss[1][v[i]]+d[i]>diss[0][u[i]]&&diss[0][u[i]]+d[i]+diss[1][v[i]]==diss[0][t]){
ans=(ans+mo-cntt[0][u[i]]*cntt[1][v[i]]%mo*cntt[0][u[i]]%mo*cntt[1][v[i]]%mo)%mo;
}
swap(u[i],v[i]);
if(diss[0][u[i]]+d[i]>diss[1][v[i]]&&diss[1][v[i]]+d[i]>diss[0][u[i]]&&diss[0][u[i]]+d[i]+diss[1][v[i]]==diss[0][t]){
ans=(ans+mo-cntt[0][u[i]]*cntt[1][v[i]]%mo*cntt[0][u[i]]%mo*cntt[1][v[i]]%mo)%mo;
}
}
cout<<ans;
return 0;
}