本题是我第一道A掉的NOI题~ 啪啪啪。。
参考了tky的论文,他的题解很详尽易懂,下面对这个经典题目的经典解法作个推导和总结。
第一个拦路虎是如何求出鼠和猫的位置为(a,b)时猫的下一步行动。我们设p[a][b]为猫位于a,鼠位于b时猫下一步走到的节点。由于这个图没有边权,所以这个p是可以通过n次BFS预处理出来的(这个BFS的细节需要特别注意,详见代码)。
然后我们考虑枚举所有情况来计算期望。
先看一般情况,设f[a][b]为猫位于a,鼠位于b时,猫抓到鼠的步数的期望,则猫走两次之后的位置为p[p[a][b], b]。然后鼠会走到相邻接点或不动,概率相等,设之为c,所以状态会转移到f[p[a][b], c]。采用记忆化搜索,枚举c,根据定义计算期望即可。
最后是DP的边界,a=b时返回0,p[a][b]=b或p[p[a][b], b]=b时返回1。
写代码的时候打错了一个变量名,RE/TLE/WA了五六次(本题总AC率>50%)。。羞耻MAX...
// BZOJ 1415
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1000+5, M=N*2;
#define rep(i,a,b) for (int i=a; i<=b; i++)
#define read(x) scanf("%d", &x)
#define fill(a,x) memset(a, x, sizeof(a))
double f[N][N];
int p[N][N], n, m, a, b, u, v;
struct Graph {
int s, to[M], pre[M], last[N], deg[N]; // deg为每个点的度
void init() { s=-1; fill(last, -1); fill(deg, 0); }
void ine(int a, int b, int d) {
s++;
to[s]=b, pre[s]=last[a];
last[a]=s;
}
void ine2(int a, int b, int d) {
ine(a, b, d);
ine(b, a, d);
deg[a]++; deg[b]++;
}
} G;
#define reg(i,s,u) for (int i=s.last[u]; i!=-1; i=s.pre[i])
double DP(int x, int y) {
int arv=p[p[x][y]][y]; // 命名来源为arrive,表示走两次以后到达的结点。用临时变量储存,使之后的代码更简洁
if (f[x][y]>0) return f[x][y];
if (x==y) return 0;
if (p[x][y]==y || arv==y) return f[x][y]=1;
double sum=DP(arv, y); // 待在原地不动
reg(i,G,y) sum+=DP(arv, G.to[i]);
return f[x][y]=sum/(G.deg[y]+1)+1;
}
int Q[N*4], d[N]; // 队列开到2*N可能不够,要多开几倍
void BFS(int s) {
int head=1, tail=1;
fill(d, -1); // 全部置为-1,方便BFS过程中判断
Q[1]=s; d[s]=0;
while (head<=tail) {
int x=Q[head++], tmp=p[s][x];
reg(i,G,x) {
int y=G.to[i];
if (d[y]==-1 || (d[x]+1==d[y] && tmp<p[s][y])) { // 注意判断条件:该节点没有被访问过或者路程相等但当前标号更小
d[y]=d[x]+1;
if (!tmp) p[s][y]=y; else p[s][y]=tmp;
Q[++tail]=y;
}
}
}
}
int main()
{
G.init();
read(n); read(m); read(a); read(b);
rep(i,1,m) read(u), read(v), G.ine2(u,v,1);
rep(i,1,n) BFS(i);
fill(f, 0);
printf("%.3lf\n", DP(a, b));
return 0;
}