题目链接:https://ac.nowcoder.com/acm/contest/886/H
题意:有一个无向连通图n个点m条边,有三个人,第一个人位于集合A里的一个点(等概率),第二个人位于集合B中的一个点(等概率),第三个人位于n个点中任意一点(等概率),A,B均为所有点的一个子集,现在这三个人要挑一个点聚会,问他们走的路程和的期望是多少。
1≤sizeof(A,B)≤min(n,20) T (1≤T≤5) 1≤n,m≤1e5
思路:对于A,B集合中的所有点各跑一遍BFS求这个点和图上所有点的最短距离,然后枚举所有A[i]B[j]点对,求出C点(第三个人位置)分别在1-N这些位置时的三个人路程和,最后除以|A|*|B|*n(方案数)即可。
下面是AC代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
typedef long long LL;
const int N=3e5+10; //建新边的极端情况是需要3n
int t,n,m,u,v;
vector<int>vec[N];
int D[2][21][N],A[21],B[21];
inline void bfs(int st,int *dis,int x) //*dis是调用数组dis[]
{
int q[N];
for(int i=1; i<=x; i++)
dis[i]=x+1;
dis[st]=0;
int l=1,r=0;
q[++r]=st;
while(l<=r)
{
int k=q[l++];
for(int i=0; i<vec[k].size(); i++)
{
int d=vec[k][i];
if(dis[d]== x+1)
{
dis[d]=dis[k]+1;
q[++r]=d;
}
}
}
}
inline LL solve(int u,int v)
{
int maxn=0;
static int dis[N]; //不加static会运行错误
for(int i=1; i<=n; i++)
{
maxn=max(maxn,D[0][u][i]+D[1][v][i]); //求图上一点到A[u],B[v]的最大值
}
for(int i=0; i<=maxn; i++)
vec[n+1+i].clear(); //要调用|A|*|B|次 所以每次都要清
for(int i=0; i<maxn; i++)
vec[n+1+i].push_back(n+1+i+1); 按距离递增连边
for(int i=1; i<=n; i++)
{
vec[n+1+D[0][u][i]+D[1][v][i]].push_back(i); //将新图和旧图连上更新最短路
}
bfs(n+1,dis,n+1+maxn); //将新图和旧图连上更新最短路
LL res=0;
for(int i=1; i<=n; i++)
res+=dis[i];
return res;
}
int main()
{
scanf("%d",&t);
int cas=0;
while(t--)
{
cas++;
scanf("%d%d",&n,&m);
for(int i=0; i<=n; i++)
vec[i].clear();
for(int i=0; i<m; i++)
{
scanf("%d%d",&u,&v);
vec[v].push_back(u);
vec[u].push_back(v);
}
for(int i=0; i<=A[0]; i++)
scanf("%d",A+i);
for(int i=0; i<=B[0]; i++)
scanf("%d",B+i);
for(int i=1; i<=A[0]; i++)
bfs(A[i],D[0][i],n);
for(int i=1; i<=B[0]; i++)
bfs(B[i],D[1][i],n);
LL fz=0,fm=1ll*A[0]*B[0]*n; //fm为情况数
for(int i=1; i<=A[0]; i++)
for(int j=1; j<=B[0]; j++)
fz+=solve(i,j);
fz-=fm; //solve函数对每种情况都多算了一个1
LL Gcd=__gcd(fz,fm);
fz/=Gcd,fm/=Gcd;
if (fm == 1)
printf("Case #%d: %lld\n",cas,fz);
else printf("Case #%d: %lld/%lld\n",cas,fz,fm);
}
}
wzdhxfjaaaaa!