CF24D Broken robot

IL.CF24D Broken robot

DP必须要有方向性。没有明确顺序的DP都是在耍流氓。这就是为什么有“树上DP”和“DAG上DP”而没有“图上DP”,图上有环就不知道应该按什么顺序做了!(像是基环树DP和仙人掌DP都是缩点了,因此顺序还是确定的;环形DP也有“断环成链”的trick)。

那如果真有DP来给你耍流氓怎么办?

还能怎么办?耍回去啊!

例如这题,有两种思路。

  1. 同一行中,转移顺序不定;但是不同行之间,转移顺序还是确定的。因此我们行与行之间以普通的DP转移;同一行中,暴力高斯消元消过去。

我们看一下怎么高斯消元。设有\(m\)\(n\)列。

则有

\(f_{i,j}=\begin{cases}[j=1]:\dfrac{f_{i,j}+f_{i-1,j}+f_{i,j+1}}{3}+1\\ [j=n]:\dfrac{f_{i,j}+f_{i-1,j}+f_{i,j-1}}{3}+1\\\text{otherwise}:\dfrac{f_{i,j}+f_{i-1,j}+f_{i,j-1}+f_{i,j+1}}{4}+1\end{cases}\)

处理一下:

\(\begin{cases}[j=1]:2f_{i,j}-f_{i,j+1}=f_{i-1,j}+3\\ [j=n]:2f_{i,j}-f_{i,j-1}=f_{i-1,j}+3\\\text{otherwise}:3f_{i,j}-f_{i,j-1}-f_{i,j+1}=f_{i-1,j}+4\end{cases}\)

这其中,上一行的DP值可以看作是常量。

这样复杂度是\(O(n^4)\),铁定过不去。

但如果我们把高斯消元的矩阵列出来\((5\times5)\)

\(\begin{bmatrix}2&-1&0&0&0\\-1&3&-1&0&0\\0&-1&3&-1&0\\0&0&-1&3&-1\\0&0&0&-1&2\end{bmatrix}\)

更大一点:

\(\begin{bmatrix}2&-1&0&\cdots&0&0&0\\-1&3&-1&\cdots&0&0&0\\0&-1&3&\cdots&0&0&0\\\vdots&\vdots&\vdots&\ddots&\vdots&\vdots&\vdots\\0&0&0&\cdots&3&-1&0\\0&0&0&\cdots&-1&3&-1\\0&0&0&\cdots&0&-1&2\end{bmatrix}\)

也就是说,它是一个非常稀疏的矩阵,并且非零元素只分布在主对角线两侧!

在这种特殊矩阵上高斯消元只需要消对角线两侧的位置即可,复杂度是\(O(n)\)的。

则总复杂度是\(O(n^2)\)的。

另外,从点\((X,Y)\)出发走到第\(n\)行,可以看作是从第\(X\)行的任何点出发,走到点\((n,Y)\)的方案数。

代码:

#include<bits/stdc++.h> 
using namespace std;
int n,m,X,Y;
double f[1010],g[1010][1010];
void Giaos(){
//	for(int i=1;i<=n;i++){for(int j=1;j<=n;j++)printf("%lf ",g[i][j]);puts("");}puts("");
	for(int i=1;i<n;i++){
		/*int mp=i;
		for(int j=i+1;j<=min(n,i+2);j++)if(fabs(g[j][i])>fabs(g[mp][i]))mp=j;
		if(mp!=i){
			for(int j=i;j<=min(n,i+2);j++)swap(g[mp][j],g[i][j]);
			swap(g[mp][n+1],g[i][n+1]);
		}
		assert(mp==i);*/
		double tmp=g[i+1][i]/g[i][i];
		g[i+1][i]=0,g[i+1][i+1]-=tmp*g[i][i+1],g[i+1][n+1]-=tmp*g[i][n+1];
	}
//	for(int i=1;i<=n;i++){for(int j=1;j<=n;j++)printf("%lf ",g[i][j]);puts("");}puts("");
	f[n]=g[n][n+1]/g[n][n];
	for(int i=n-1;i>=1;i--)f[i]=(g[i][n+1]-g[i][i+1]*f[i+1])/g[i][i];
}
int main(){
	scanf("%d%d%d%d",&m,&n,&X,&Y),m-=X-1,X=1;
	if(m==1){puts("0");return 0;}
	if(n==1){printf("%d\n",(m-1)*2);return 0;}
	for(int i=1;i<m;i++){
		g[1][1]=2,g[1][2]=-1,g[1][n+1]=f[1]+3;
		g[n][n]=2,g[n][n-1]=-1,g[n][n+1]=f[n]+3;
		for(int j=2;j<n;j++)g[j][j-1]=g[j][j+1]=-1,g[j][j]=3,g[j][n+1]=f[j]+4;
		Giaos();
	}
	printf("%lf\n",f[Y]);
	return 0;
}
  1. 因为“保留4位小数”,所以……

\(50\)遍最普通的DP完事。

代码:

#include<bits/stdc++.h> 
using namespace std;
int n,m,X,Y;
double f[1010][1010];
int main(){
	scanf("%d%d%d%d",&m,&n,&X,&Y),m-=X-1,X=1;
	if(m==1){puts("0");return 0;}
	if(n==1){printf("%d\n",(m-1)*2);return 0;}
	for(int i=1;i<m;i++)for(int tmp=1;tmp<=50;tmp++)for(int j=1;j<=n;j++){
		if(j==1)f[i][j]=(f[i][j+1]+f[i][j]+f[i-1][j])/3+1;
		else if(j==n)f[i][j]=(f[i][j-1]+f[i][j]+f[i-1][j])/3+1;
		else f[i][j]=(f[i-1][j]+f[i][j]+f[i][j-1]+f[i][j+1])/4+1;
	}
	printf("%lf\n",f[m-1][Y]);
	return 0;
}

posted @ 2021-03-30 16:19  Troverld  阅读(72)  评论(0编辑  收藏  举报