BZOJ 1415 - 利用定义计算期望 + DP
本题是我第一道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; }