[2019.3.7]BZOJ1415 [Noi2005]聪聪和可可
首先,我们记录\(mo_{i,j}\)表示在点\(i\)到点\(j\)的任意一条最短路上,并且和\(i\)相邻的标号最小的节点,也就是当聪聪在点\(i\),可可在点\(j\)时,聪聪下一步会走的节点。
显然可以对每一个点\(i\)跑最短路计算\(mo_i\)。
使用堆优化的Dijkstra的话这部分的总时间复杂度是\(O(n^2\log 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;
}