很水的dp,用于在你蓝题dp做疯了的时候来提高下信心。
首先那个距离就是曼哈顿距离,所以小于等于1的格子就是当前格子的上下左右,还包含自身。
其实唯一的问题就是如何计算贡献的时候不要算重复了,那么分情况考虑就可以不算重复,f[i][j][0]代表到达ij点的走法是从上面走下来,f[i][j][1]代表到达ij点的走法是从左边走过来。
那么到达ij点后,有向下走和向右走,当你画出了图,就什么都知道了。
黑色线代表的是当前的ij节点以及他旁边被算过的格子。
蓝色线代表的是走到ij点的那个点在哪里(上面还是左边),和他旁边被算过的格子。
红色线代表的是ij点下一步怎么走,以及他所需要计算的格子。
黄色荧光笔就是你需要新计算的内容啦(没有画到的内容你看是不是都已经被其他的格子覆盖过一次了呢?)
有了这个图,我相信方程就是大家都会写了,只需要走的那一步加上黄色荧光笔所在格子的贡献就可以啦。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<time.h>
#include<string>
#include<cmath>
#include<map>
#include<set>
#define ll long long
//#define double long double
using namespace std;
const long long max_ = 1000 + 7;
const int mod = 1e9 + 7;
const int inf = 1e9;
const long long INF = 1e18;
int read() {
	int s = 0, f = 1;
	char ch = getchar();
	while (ch<'0' || ch>'9') {
		if (ch == '-')
			f = -1;
		ch = getchar();
	}
	while (ch >= '0'&&ch <= '9') {
		s = s * 10 + ch - '0';
		ch = getchar();
	}
	return s * f;
}

int min(int a, int b) {
	return a < b ? a : b;
}
int max(int a, int b) {
	return a > b ? a : b;
}
int node[max_][max_],f[max_][max_][5];
int main() {
	int n = read(), m = read();
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			node[i][j] = read();
		}
	}
	memset(f, 1, sizeof(f));
	f[1][1][0] = f[1][1][1] = node[1][1] + node[1][2] + node[2][1];
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			f[i+1][j][0] = min(f[i][j][0] + node[i + 1][j + 1] + node[i + 1][j - 1] + node[i + 2][j], f[i + 1][j][0]);
			f[i][j + 1][1] = min(f[i][j][0] + node[i][j + 2] + node[i + 1][j + 1], f[i][j + 1][1]);

			f[i + 1][j][0] = min(f[i][j][1] + node[i + 1][j + 1] + node[i + 2][j], f[i + 1][j][0]);
			f[i][j + 1][1] = min(f[i][j][1] + node[i-1][j + 1] + node[i][j + 2] + node[i+1][j+1], f[i][j + 1][1]);
		}
	}
	cout << min(f[n][m][0],f[n][m][1]);
	return 0;
}