做题笔记:CF24D

这是一道高斯消元+简单期望DP的好题,有人说这是道算黑题的紫题。

刚开始也是卡在了高斯消元优化这一步,所以查了一些资料,调了一个小时的代码后终于A掉了。

这道题对我启发巨大,所以来写一篇做题笔记,想做或正在做此题的人也可以当作不发布的民间题解。

正文开始

luogu题面戳这

CF原题面戳这

理解题意

有一个\(n\)\(m\)列的矩阵,有一个机器人原在矩阵方格\((x,y)\)处,每次等概率进行以下操作,但不能出边界:向左,右,下方移动一个单位,或者不动。

问移动到最后一行(即第\(n\)行)的期望

主要是期望,其它很好理解 附:期望的概念

思路分析

毕竟此题有一部分是期望DP,所以肯定要推每个方格的状态转移方程。

\(f[i][j]\)表示从方格坐标\((i,j)\)处移到第\(n\)行的期望值。

可以很轻易地推出状态转移方程:

首先\(i=n\),即处于最后一行,废话什么,当然全部是\(0\)

然后考虑三种情况:

\(j=1\),即处于最左边,此时可以从自己,右边,下边转移过来。

所以方程为:

\(f[i][1]=\frac{f[i][1]+f[i][2]+f[i+1][1]}{3}+1\)

同理,当\(j=m\),此时在最右边,可以从自己,左边,下边转移过来。

方程为:

\(f[i][m]=\frac{f[i][m]+f[i][m-1]+f[i+1][m]}{3}+1\)

还有一种情况大家肯定想得到,就是\(j\ne 1,j\ne m\),即不在最左边,也不在最右边,左,右,下都有方格。

此时自己,左边,右边,下边都可以进行转移。

显而易见这时候方程长这样:

\(f[i][j]=\frac{f[i][j]+f[i][j-1]+f[i][j+1]+f[i+1][j]}{4}+1\)

这时候就能快乐的进行转移了......

?

相信注意到了,这个方程和以往的DP不太一样,这关系之间怎么有环?

别急,这题不是还有一个标签吗?高斯消元

建立消元矩阵

我们先将三个方程移项了,将\(f[i+1][j]\)\(+1\)留在方程右边,其它,统统移过去。

第一个方程\((j=1)\)可变为:

\(\frac{2}{3}f[i][1]-\frac{1}{3}f[i][2]=\frac{1}{3}f[i+1][1]+1\)

第二个方程\((j=m)\)可变为:

\(-\frac{1}{3}f[i][m-1]+\frac{2}{3}f[i][m]=\frac{1}{3}f[i+1][m]+1\)

第三个方程\((j\ne 1,j\ne m)\)可变为:

\(-\frac{1}{4}f[i][j-1]+\frac{3}{4}f[i][j]-\frac{1}{4}f[i][j+1]=\frac{1}{4}f[i+1][j]+1\)

现在,如果我们把\(f[i+1][j]\)当作已知数的话,就可以显而易见列出矩阵:

\(\begin{bmatrix}\frac{2}{3}&-\frac{1}{3}&0&0&0&|&\frac{1}{3}f[i+1][j]+1\\-\frac{1}{4}&\frac{3}{4}&-\frac{1}{4}&0&0&|&\frac{1}{4}f[i+1][j]+1\\0&-\frac{1}{4}&\frac{3}{4}&-\frac{1}{4}&0&|&\frac{1}{4}f[i+1][j]+1\\0&0&-\frac{1}{4}&\frac{3}{4}&-\frac{1}{4}&|&\frac{1}{4}f[i+1][j]+1\\0&0&0&-\frac{1}{3}&\frac{2}{3}&|&\frac{1}{3}f[i+1][j]+1\end{bmatrix}\)

写矩阵真的太累了

对于如何求\(f[i+1][j]\)使矩阵右边变成已知数,我们不妨从第\(n-1\)行倒推到第\(x\),依次求出每一行所有数的\(f\)值就行,再将第\(i\)行的结果用于第\(i-1\)行的求解。

所以思路已经很明显了,从\(n-1\)行倒推,对每行进行一次高斯消元求出当前行的所有\(f\)值,再用于上面一行的求解,直到第\(x\)行。

关于高斯消元部分(本题妙点,敲黑板敲黑板)

...你看直接对每行用裸的高斯消元的话复杂度是\(O(nm^2)\),而这题\(n,m\)达到了\(10^3\)级别,岂不是起飞了(

所以需要对裸的高斯消元优化

再次观察矩阵,发现这个矩阵形如这样(非\(0\)数在这里设为\(1\)):

\(\begin{bmatrix}1&1&0&0&0&|&1\\1&1&1&0&0&|&1\\0&1&1&1&0&|&1\\0&0&1&1&1&|&1\\0&0&0&1&1&|&1\end{bmatrix}\)

由图发现规律:第\(1\)行和第\(m\)行 (即最后一行) 只有两个非\(0\)数,另外\(2\)\(m-1\)每行有三个,其他部分则构成了两个全\(0\)的上三角和下三角。

所以我们可以在枚举矩阵每一行时直接选取当前行,同时只削掉下一行的三个数,得到了一个这样的矩阵。

\(\begin{bmatrix}1&1&0&0&0&|&1\\0&1&1&0&0&|&1\\0&0&1&1&0&|&1\\0&0&0&1&1&|&1\\0&0&0&0&1&|&1\end{bmatrix}\)

我们惊奇地发现:

现在只要从最后一行往上反推,就能求出所有答案!

我们顺利地将算法优化到了\(O(nm)\),显然已经是本题正解了。

...吗?

特判

这题还有一个细节叫特判。

上面推式子时还没讲,特地放在这里强调。

想一下,如果\(m=1\)呢?

此时只能向下移动,每一个方格的状态转移方程变为:

\(f[i][1]=\frac{f[i+1][1]+f[i][1]}{2}+1\)

将它转换一下,易得:

\(f[i][1]=f[i+1][1]+2\)

显然这时又可以直接推出第\(i\)行第\(n\)行的关系了:

\(f[i][1]=f[n][1]+2\times (n-x)\)

由于第\(n\)行的\(f\)值全部为\(0\),所以此时\(2\times (n-x)\)就是答案,特判输出即可。

终于可以快乐地码它了)

code

#include <bits/stdc++.h>
using namespace std;
int n,m,x,y;
double f[1005][1005],a[1005][1005];
//f的意义同上,a是每一行的消元矩阵 
void gauss()
{
	for(int i=1;i<=m;++i)
	{
		double r=a[i][i];
		a[i][i]/=r,a[i][i+1]/=r;
		if(i<m) a[i][m+1]/=r;
		double t=a[i+1][i];
		int d[3]={i,i+1,m+1};
		for(int j=0;j<3;++j)
		{
			a[i+1][d[j]]-=t*a[i][d[j]];
		}
	}//消元 
	for(int i=m;i;--i)
	{
		a[i-1][m+1]-=a[i-1][i]*a[i][m+1];
		a[i-1][i]-=a[i-1][i]*a[i][i];
	}//倒推 
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&x,&y);
	if(m==1) printf("%.10lf\n",(double)(2*(n-x)));//注意特判! 
	else
	{
		for(int i=n-1;i>=x;--i)
		{
			a[1][1]=2.0/3,a[1][2]=-1.0/3,a[1][m+1]=f[i+1][1]/3+1;
			a[m][m]=2.0/3,a[m][m-1]=-1.0/3,a[m][m+1]=f[i+1][m]/3+1;
			//处理第i行消元矩阵的第1,m行 
			for(int j=2;j<m;++j)
			{
				a[j][j-1]=-1.0/4,a[j][j]=3.0/4,a[j][j+1]=-1.0/4;
				a[j][m+1]=f[i+1][j]/4+1;
			}//处理第i行消元矩阵的第2至m-1行
			gauss();
			for(int j=1;j<=m;++j)
			{
				f[i][j]=a[j][m+1];
			}//转移答案 
		}
		printf("%.10lf\n",f[x][y]);
	}
	return 0;
}
posted @ 2023-02-02 10:44  Oier_szc  阅读(91)  评论(0)    收藏  举报