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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值