[2019.3.7]BZOJ1415 [Noi2005]聪聪和可可
首先,我们记录(mo_{i,j})表示在点(i)到点(j)的任意一条最短路上,并且和(i)相邻的标号最小的节点,也就是当聪聪在点(i),可可在点(j)时,聪聪下一步会走的节点。
显然可以对每一个点(i)跑最短路计算(mo_i)。
使用堆优化的Dijkstra的话这部分的总时间复杂度是(O(n^2log n))。
然后进行dfs,记(dfs(x,y))表示聪聪在(x),可可在(y)时的期望步数。
那么:
当(x=y),直接返回0;
当(mo_{x,y}=y)或(mo_{mo_{x,y},y}=y),即聪聪走两步以下就可以抓到可可,返回1;
否则,设(mo_{mo_{x,y},y}=to),与(y)相邻的节点数量为(m),分别是(p_1,p_2,p_3,...,p_m)。
那么答案=(frac{sum_{t=1}^m[dfs(to,p_i)+1]+dfs(to,y)}{m+1})
显然不会死循环,因为每一次递归聪聪和可可的距离至少减少1。
可是这样会TLE。
所以我们要记忆化搜索。
code:
#include<bits/stdc++.h>
using namespace std;
const int INF=1e9;
struct edge{
int t,nxt;
}e[2010];
struct node{
int id,v;
bool operator>(const node&y)const{
return v==y.v?id<y.id:v<y.v;
}
bool operator<(const node&y)const{
return v==y.v?id>y.id:v>y.v;
}
}t;
int n,m,u,v,a,b,p[1010],cnt,be[1010],mo[1010][1010],vis[1010],mn[1010];
double s[1010][1010];
priority_queue<node>q;
void add(int x,int y){
e[++cnt].t=y,e[cnt].nxt=be[x],be[x]=cnt;
}
void Dijkstra(int x){
for(int i=1;i<=n;++i)mn[i]=INF,vis[i]=0;
mn[x]=0,mo[x][x]=0,q.push((node){x,0});
while(!q.empty()){
while(!q.empty()&&(t=q.top(),vis[t.id]))q.pop();
if(q.empty())return;
q.pop(),vis[t.id]=1;
for(int i=be[t.id];i;i=e[i].nxt)mo[x][e[i].t]>mo[x][t.id]&&mn[e[i].t]==mn[t.id]+1?mo[x][e[i].t]=(t.id==x?e[i].t:mo[x][t.id]):0,mn[e[i].t]>t.v+1?mn[e[i].t]=t.v+1,mo[x][e[i].t]=(t.id==x?e[i].t:mo[x][t.id]),q.push((node){e[i].t,mn[e[i].t]}),0:0;
}
}
double dfs(int x,int y){
if(x==y)return 0;
if(s[x][y])return s[x][y];
if(mo[x][y]==y)return s[x][y]=1;
int to=mo[mo[x][y]][y];
if(to==y)return s[x][y]=1;
double ans=0;
for(int i=be[y];i;i=e[i].nxt)ans+=dfs(to,e[i].t)+1;
ans+=dfs(to,y)+1;
return s[x][y]=1.0*ans/p[y];
}
int main(){
scanf("%d%d%d%d",&n,&m,&a,&b);
for(int i=1;i<=m;++i)scanf("%d%d",&u,&v),add(u,v),add(v,u),++p[u],++p[v];
for(int i=1;i<=n;++i)++p[i],Dijkstra(i);
printf("%.3lf",dfs(a,b));
return 0;
}