ZROI 2021 10联day8 T1 题(期望+二维区间DP)

你要在一片菜地里捉兔子。

菜地形如一个一个 N×M 的长方形网格,每个顶点要么是空的,要么有一个兔子洞。在每个洞里有恰好 44 只兔子。在土地的四个角都设置了逮兔陷阱(陷阱在坐标 [0,0],[0,M],[N,0],[N,M] 上)。

你有一个彩弹枪和 K 颗彩弹,其中 K 是兔子洞的个数。所有的彩弹颜色都不一样,它们编号从 1 到 K 。你可以往任意兔子洞射一个彩弹。射出之后,该兔子洞的所有兔子都被染成彩弹的颜色,然后都跑出来,沿着四个方向逃窜。全部四只兔子的速度相同,兔子只能沿着网格边界跑。

在移动的过程中,每只兔子都在顶点上或者在边上会留下脚印。但是,在任意顶点或边上,只有最早的脚印是可见的。当一只兔子进入一个已经被染色的顶点时,它会用以下方法选择方向:如果在它前面的那条边与它现在所在的点的颜色不同,它就会顺时针旋转90度,直到颜色相同为止。旋转不耗时间。旋转后,兔子以原速度继续跑。

当兔子遇到网格边界时,它会顺时针旋转90 度继续跑,直到它掉进陷阱。兔子掉进陷阱就认为被抓住了。

如果一只兔子在跑的过程中,见到一个非空的兔子洞,它会立即通知(不耗时间)洞里的所有兔子从秘密隧道逃走,这个洞里的兔子永远都不会在土地中出现。通知洞内兔子后,该兔子仍继续跑。

你的行动是:随机往某个非空的兔子洞射一颗彩弹,然后等待出来的所有兔子都被抓住,再随机往另一个非空兔子洞射一颗,再等 ⋯⋯⋯⋯ 直到所有兔子洞都空为止。每一次你选择每个非空兔子洞的概率都是均等的。

现在,你的任务是求出所有跑出来的兔子从跑出来的时刻起至被抓住的路径之和的数学期望 L 。

考试感想

作为NOIP模拟赛的T1,竟然是全场提交数最少的一道题,而且我觉得暴力比正解还难写
PS:这场10联题目名叫《题》《目》《不》《难》,结果全场第一才180分,要知道原来就算题目再难也还是有300+或者AK的
不过看了题解觉得T1还是可以做的

解题思路

F [ i ] [ j ] [ l ] [ r ] F[i][j][l][r] F[i][j][l][r]表示这一块矩形内的期望步数,我们为了维护 F F F,还需要开个数组 G [ i ] [ j ] [ l ] [ r ] [ 0 / 1 / 2 / 3 ] G[i][j][l][r][0/1/2/3] G[i][j][l][r][0/1/2/3],表示分别到达这块矩形四个顶点的期望步数
画个图,我们给这个网格的四个顶点这样顺序标成 0 、 1 、 2 、 3 0、1、2、3 0123
在这里插入图片描述
然后我们枚举这个矩形内第一个被攻击的兔子洞,发现这个兔子洞的兔子把矩形分成了四个子矩形,且这个兔子洞的路线已经限定了子矩形中的兔子最终会到达子矩形的四个顶点,这也就是最优子结构,可以进行动态规划
在这里插入图片描述
先考虑左上角的矩阵,我们看这个矩形内的兔子跑到了哪里在这里插入图片描述
先考虑G数组的更新
我们可以发现左上角子矩形的0点转移到了当前点的1点,子矩形的1号点现在还在1点,子矩形的2号点现在转移到了当前矩形的2号点,子矩形的三号点转移到了当前矩形的0号点(图中绿色剪头)

	G[x][0]+=G[y][3];
	G[x][1]+=(G[y][0]+G[y][1]);
	G[x][2]+=G[y][2];

接着思考F数组的转移,设枚举的第一个兔子洞是(i,j)
在这里插入图片描述
我们先加上子矩形中的步数

F[x]+=F[y]

这样可以算出每个洞中的兔子走的步数,乘上对应的G就是这个子矩形内所有兔子的步数,注意边界上的兔子洞不能转移,因为题目限制经过的兔子洞中的兔子不会在跑,而这个边界正是你枚举的第一个兔子洞中的兔子的行进路线,所以不能转移

	int y=note[Up][i][Left][j];
	F[x]+=F[y];
	G[x][0]+=G[y][3];
	G[x][1]+=(G[y][0]+G[y][1]);
	G[x][2]+=G[y][2];
	F[x]+=G[y][0]*(i-Up);
	F[x]+=G[y][2]*(Right-j);
	F[x]+=G[y][3]*(Down-i+j-Left);

对于其余三个矩形类似处理,可以得到下面的转移式

			int y=note[Up][i][Left][j];
			F[x]+=F[y];
			G[x][0]+=G[y][3];
			G[x][1]+=(G[y][0]+G[y][1]);
			G[x][2]+=G[y][2];
			F[x]+=G[y][0]*(i-Up);
			F[x]+=G[y][2]*(Right-j);
			F[x]+=G[y][3]*(Down-i+j-Left);
			y=note[Up][i][j][Right];
			F[x]+=F[y];
			G[x][1]+=G[y][0];
			G[x][2]+=(G[y][2]+G[y][1]);
			G[x][3]+=G[y][3];
			F[x]+=G[y][1]*(Right-j);
			F[x]+=G[y][3]*(Down-i);
			F[x]+=G[y][0]*(j-Left+i-Up);
			y=note[i][Down][Left][j];
			F[x]+=F[y];
			G[x][0]+=(G[y][0]+G[y][3]);
			G[x][1]+=G[y][1];
			G[x][3]+=G[y][2];
			F[x]+=G[y][1]*(i-Up);
			F[x]+=G[y][2]*(Right-j+Down-i);
			F[x]+=G[y][3]*(j-Left);
			y=note[i][Down][j][Right];
			F[x]+=F[y];
			G[x][0]+=G[y][0];
			G[x][2]+=G[y][1];
			G[x][3]+=(G[y][3]+G[y][2]);
			F[x]+=G[y][0]*(j-Left);
			F[x]+=G[y][1]*(Right-j+i-Up);
			F[x]+=G[y][2]*(Down-i);

最后是当前兔子洞的贡献
乘上这个概率, 1 c n t \frac{1}{cnt} cnt1,然后考虑第一个兔子洞的路线,如果平移一下可以发现,四个边界肯定都要走一次,所以

	F[x]=F[x]/(db)cnt+2*(Right-Left+Down-Up);

然后是G,发现当前兔子洞使四个角落的点都多了一只兔子

	for(int i=0;i<4;i++)
	G[x][i]=G[x][i]/(db)cnt+1;

至于整体的转移,可以记忆化搜索,也可以类似区间DP,从小到大枚举举行的长宽,也就是二维区间DP

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double db;
const int N = 1005;
db F[N*N],G[N*N][4];
int note[50][50][50][50];
int n,m,k,tot=0;
int rab[50][50];
void dp(int Left,int Right,int Up,int Down)
{
	int cnt=0;
	for(int i=Up+1;i<Down;i++)
	for(int j=Left+1;j<Right;j++)
	if(rab[i][j]) cnt++;
	if(cnt==0) return;
	int x=note[Up][Down][Left][Right];
	for(int i=Up+1;i<Down;i++)
	{
		for(int j=Left+1;j<Right;j++)
		{
			if(!rab[i][j]) continue;
			int y=note[Up][i][Left][j];
			F[x]+=F[y];
			G[x][0]+=G[y][3];
			G[x][1]+=(G[y][0]+G[y][1]);
			G[x][2]+=G[y][2];
			F[x]+=G[y][0]*(i-Up);
			F[x]+=G[y][2]*(Right-j);
			F[x]+=G[y][3]*(Down-i+j-Left);
			y=note[Up][i][j][Right];
			F[x]+=F[y];
			G[x][1]+=G[y][0];
			G[x][2]+=(G[y][2]+G[y][1]);
			G[x][3]+=G[y][3];
			F[x]+=G[y][1]*(Right-j);
			F[x]+=G[y][3]*(Down-i);
			F[x]+=G[y][0]*(j-Left+i-Up);
			y=note[i][Down][Left][j];
			F[x]+=F[y];
			G[x][0]+=(G[y][0]+G[y][3]);
			G[x][1]+=G[y][1];
			G[x][3]+=G[y][2];
			F[x]+=G[y][1]*(i-Up);
			F[x]+=G[y][2]*(Right-j+Down-i);
			F[x]+=G[y][3]*(j-Left);
			y=note[i][Down][j][Right];
			F[x]+=F[y];
			G[x][0]+=G[y][0];
			G[x][2]+=G[y][1];
			G[x][3]+=(G[y][3]+G[y][2]);
			F[x]+=G[y][0]*(j-Left);
			F[x]+=G[y][1]*(Right-j+i-Up);
			F[x]+=G[y][2]*(Down-i);
	    }
	}
	F[x]=F[x]/(db)cnt+2*(Right-Left+Down-Up);
	for(int i=0;i<4;i++)
	G[x][i]=G[x][i]/(db)cnt+1;
}
int main()
{	
	cin>>n>>m>>k;
	for(int i=1;i<=k;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		rab[x][y]=1;
	}
	for(int i=0;i<=n;i++)
	for(int j=i;j<=n;j++)
	for(int k=0;k<=m;k++)
	for(int l=k;l<=m;l++)
	note[i][j][k][l]=++tot;
	for(int len1=1;len1<=n;len1++)
	for(int len2=1;len2<=m;len2++)
	for(int i=0;i+len1<=n;i++)
	for(int j=0;j+len2<=m;j++)
	dp(j,j+len2,i,i+len1);
	printf("%.15lf",F[note[0][n][0][m]]);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值