做题笔记:CF24D
更好的阅读体验
这是一道高斯消元+简单期望DP的好题,有人说这是道算黑题的紫题
刚开始也是卡在了高斯消元优化这一步,所以查了一些资料,调了一个小时的代码后终于A掉了
这道题对我启发巨大,所以来写一篇做题笔记,想做或正在做此题的人也可以当作不发布的民间题解
正文开始
luogu题面戳这
CF原题面戳这
理解题意
有一个
只要知道期望是什么意思,即使刚入门的Oier也很好理解题意,不再赘述 附:期望的概念
思路分析
毕竟此题有一部分是期望DP,所以肯定要推每个方格的状态转移方程
设
可以很轻易地推出状态转移方程:
首先当
然后考虑三种情况:
当
所以方程为:
同理,当
方程为:
还有一种情况大家肯定想得到,就是
此时自己,左边,右边,下边都可以进行转移
显而易见这时候方程长这样:
这时候就能快乐的进行转移了......
?
相信注意到了,这个方程和以往的DP不太一样,这关系之间怎么有环?
别急,这题不是还有一个标签吗?高斯消元
建立消元矩阵
我们先将三个方程移项了,将
第一个方程
第二个方程
第三个方程
现在,如果我们把
写矩阵真的太累了
对于如何求
所以思路已经很明显了,从
关于高斯消元部分(本题妙点,敲黑板敲黑板)
...你看直接对每行用裸的高斯消元的话复杂度是
所以需要对裸的高斯消元优化
再次观察矩阵,发现这个矩阵有规律:第
所以我们可以在枚举矩阵每一行时直接选取当前行,同时只削掉下一行的三个数,得到了一个这样的矩阵(非
我们惊奇地发现:
现在只要从最后一行往上反推,就能求出所有答案!
我们顺利地将算法优化到了
...吗?
特判
这题还有一个细节叫特判
上面推式子时还没讲,特地放在这里强调
想一下,如果
此时只能向下移动,每一个方格的状态转移方程变为:
将它转换一下,易得:
显然这时又可以直接推出第
由于第
终于可以快乐地码它了)
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;
}