题目链接:传送门
题意:在n*m的平面上,每个点都具有贡献,问你从(1,1)到(n,m)找到一条路径(只能往上或者往右),使得路径上的点的方差最小。询问最小的方差值为多少。
解决方法:首先根据简化所给公式可以推出一个式子。推出式子的步骤如下
公式递推很简单。可以忽略。
下一步我们就可以用一个dp[i][j][k]来表示走到(i,j)时序列和为k(序列和不会超过(30+30-1)*30)时的平方和最小值,注意一下dp数组的初始化,然后暴力更新就行了。
向上的方程:dp[i + 1][z][k + ax[i + 1][z]] = min(dp[i + 1][z][k + ax[i + 1][z]], dp[i][z][k] + ax[i + 1][z] * ax[i + 1][z]);
向右的方程:dp[i][z + 1][k + ax[i][z + 1]] = min(dp[i][z + 1][k + ax[i][z + 1]], dp[i][z][k] + ax[i][z + 1] * ax[i][z + 1]);
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<string>
#include<iostream>
#include<map>
#include<vector>
#include<set>
#include<queue>
using namespace std;
const int inf = 0x3f3f3f3f;
typedef long long ll;
int ax[50][50];
ll dp[50][50][2000];
int main(void) {
int t;
scanf("%d", &t);
int zzz = 1;
while (t--) {
int n, m;
int tot = 0;
scanf("%d%d", &n, &m);
int maxx = 0;
for (int i = 1; i <= n; i++){
for (int z = 1; z <= m; z++) {
scanf("%d", &ax[i][z]);
maxx = max(ax[i][z], maxx);
}
}
printf("Case #%d: ", zzz++);
for (int i = 0; i <= n; i++) {
for (int z = 0; z <= m; z++) {
for (int j = 0; j <= (n + m - 1)*maxx+10; j++) {
dp[i][z][j] = inf;
}
}
}
dp[1][1][ax[1][1]] = ax[1][1] * ax[1][1];
for(int i=1;i<=n;i++){
for (int z = 1; z <= m; z++) {
for (int k = 0; k <= (n + m - 1)*maxx + 10; k++) {
if (dp[i][z][k] != inf) {
if (i + 1 <= n) {
dp[i + 1][z][k + ax[i + 1][z]] = min(dp[i + 1][z][k + ax[i + 1][z]], dp[i][z][k] + ax[i + 1][z] * ax[i + 1][z]);
}
if (z + 1 <= m) {
dp[i][z + 1][k + ax[i][z + 1]] = min(dp[i][z + 1][k + ax[i][z + 1]], dp[i][z][k] + ax[i][z + 1] * ax[i][z + 1]);
}
}
}
}
}
ll ans = inf;
for (ll i = 0; i <= (n+m-1)*maxx + 10; i++) {
if (dp[n][m][i] != inf) {
ans = min(ans, (n + m - 1)*dp[n][m][i] - i*i);
}
}
printf("%lld\n", ans);
}
return 0;
}