题目链接: https://ac.nowcoder.com/acm/contest/886/H
题意:
一个 1 e 5 1e5 1e5的点 1 e 5 1e5 1e5条边的图,小A,小B,小C,三个人想在图中选择一个点 ,使得三个人所走的路径和最小。现在小A等概率的出现在集合A中的点上,小B等概率的出现在集合B中的点上, ∣ S a ∣ = m i n ( 20 , n ) , ∣ S b ∣ = m i n ( 20 , n ) |S_{a}|=min(20,n) ,|S_{b}|=min(20,n) ∣Sa∣=min(20,n),∣Sb∣=min(20,n) ,小C等概率的出现在任何一个点上,问所有人走的路径最短的期望是多少。
做法:
关键点一:枚举
在不管小C的前提下,我们可以得到的是小A和小B共同走的最短路径和,因为集合的大小只有20,数据仿佛并没有卡SPFA,所以枚举每对点A和点B去求带着C的情况是可以过的。
关键点二: 加点后将路径长拆成点
我们先将
d
i
s
[
i
]
=
d
i
s
A
[
i
]
+
d
i
s
B
[
i
]
dis[i]=disA[i]+disB[i]
dis[i]=disA[i]+disB[i] ,表示A和B走到这个点的最短路,同时先处理出最长的路径长
m
a
x
l
e
n
maxlen
maxlen。因为小C的位置是未知的,所以我们考虑增加一个点
P
n
+
1
P_{n+1}
Pn+1 代表点C,先处理出点A和点B会,点
P
n
+
2
P_{n+2}
Pn+2 代表和点C距离为1的点,
P
n
+
3
P_{n+3}
Pn+3 代表和点C距离为2的点…依此类推,一直到
m
a
x
l
e
n
maxlen
maxlen.然后我们就考虑加边,从
P
n
+
1
+
d
i
s
A
[
i
]
+
d
i
s
B
[
i
]
P_{n+1+disA[i]+disB[i]}
Pn+1+disA[i]+disB[i] 向点
P
i
P_{i}
Pi 加一条边,之后将
P
n
+
1
P_{n+1}
Pn+1向
P
n
+
2
P_{n+2}
Pn+2连边,
P
n
+
2
P_{n+2}
Pn+2向
P
n
+
3
P_{n+3}
Pn+3连边,表示路径每加1需要给长度加1。最后再把1到n的
d
i
s
dis
dis加进答案里。
这里可能一下子难以理解是为什么,我画个图。
从上面的图中我们可以看出来,如果我们把点C放在每个点上之后的答案,比如点2,我们只要把终点设在点1就可以保证我们能让路径总和最小为3,如果正常让A和B走的话到这里应该是4,这样就会将最短路变大。所以我们要将需要到每个点的最短路处理出来之后还要再和原来的边跑一遍最短路,如果能更新,那就是可以在点C上做更新的。 当然每次做完在最后我们要减掉
n
n
n,因为每次的路径长度因为从
P
i
+
x
P_{i+x}
Pi+x 跑到点
P
i
P_{i}
Pi又多加了1,所以要减掉。
分母因为枚举了每个位置所以就是
s
z
a
∗
s
z
b
∗
n
sza*szb*n
sza∗szb∗n这个数字,分子
就是我们通过上面的过程计算出的答案。
代码
#include <bits/stdc++.h>
#define rep(i,a,b) for(int i=(int)a;i<=(int)b;i++)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int maxn=100005;
const int maxm=200005;
int n,m,dis_a[21][maxn],dis_b[21][maxn];
int A[21],B[21],dis[maxm],in[maxn],sza,szb;
ll Mole,Deno;
vector<int> ve[maxm];
void add(int u,int v){
//printf("from %d to %d\n",u,v);
ve[u].push_back(v);
}
void bfs(int st,int sz){
rep(i,1,sz) dis[i]=inf;
dis[st]=0;
queue<int> Q;
Q.push(st); in[st]=1;
while(!Q.empty()){
int u=Q.front(); Q.pop();
in[u]=0;
for(int i=0;i<ve[u].size();i++){
int v=ve[u][i];
if(dis[v]>dis[u]+1){
dis[v]=dis[u]+1;
if(!in[v]) {
Q.push(v); in[v]=1;
}
}
}
}
}
void deal(int ida,int idb){
int mdi=0;
rep(i,1,n) mdi=max(mdi,dis_a[ida][i]+dis_b[idb][i]);
rep(i,n+1,n+1+mdi) ve[i].clear();
rep(i,1,n){
add(n+1+dis_a[ida][i]+dis_b[idb][i],i);
}
rep(i,n+1+1,n+1+mdi) add(i-1,i);
bfs(n+1,n+1+mdi);
rep(i,1,n) Mole+=dis[i];
//printf("ida = %d , idb = %d , Mole = %lld\n",ida,idb,Mole);
}
int main(){
int T,cas=0; scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
rep(i,1,n) ve[i].clear();
rep(i,1,m) {
int x,y;scanf("%d%d",&x,&y);
add(x,y); add(y,x);
}
scanf("%d",&sza);
rep(i,1,sza) {
scanf("%d",&A[i]);
bfs(A[i],n);
rep(j,1,n){
dis_a[i][j]=dis[j];
}
}
scanf("%d",&szb);
rep(i,1,szb) {
scanf("%d",&B[i]);
bfs(B[i],n);
rep(j,1,n){
dis_b[i][j]=dis[j];
}
}
Mole=0;
rep(i,1,sza){
rep(j,1,szb){
deal(i,j);
}
}
Deno=1ll*sza*szb*n;
Mole-=Deno;
ll gcd=__gcd(Mole,Deno);
Mole/=gcd,Deno/=gcd;
if(Deno==1) printf("Case #%d: %lld\n",++cas,Mole);
else printf("Case #%d: %lld/%lld\n",++cas,Mole,Deno);
}
return 0;
}
/*
*/