优秀路线
题目链接:ybtoj高效进阶 21283
题目大意
给你一个二维网格,要你选一条左上到右下的路径,只能向右向下走,使得路径上的数方差最小。
思路
方差嘛,直接开拆!
(
x
i
−
x
0
)
2
(x_i-x_0)^2
(xi−x0)2
∑
x
i
2
−
∑
2
x
i
x
0
+
n
x
0
2
\sum x_i^2-\sum2x_ix_0+nx_0^2
∑xi2−∑2xix0+nx02
(
x
0
=
∑
x
i
/
n
)
(x_0=\sum x_i/n)
(x0=∑xi/n)
∑
x
i
2
−
∑
2
x
i
x
0
+
∑
x
i
x
0
\sum x_i^2-\sum2x_ix_0+\sum x_i x_0
∑xi2−∑2xix0+∑xix0
∑
x
i
2
−
∑
x
i
x
0
\sum x_i^2-\sum x_ix_0
∑xi2−∑xix0
∑
x
i
2
−
(
∑
x
i
)
2
/
n
\sum x_i^2-(\sum x_i)^2/n
∑xi2−(∑xi)2/n
n
∑
x
i
2
−
(
∑
x
i
)
2
n\sum x_i^2-(\sum x_i)^2
n∑xi2−(∑xi)2
(这个是比赛的时候拆的,最后答案乘了
n
2
n^2
n2)
再看范围很小。
考虑直接暴力 DP。
直接设
f
i
,
j
,
k
f_{i,j,k}
fi,j,k 为当前到
(
i
,
j
)
(i,j)
(i,j) 的位置,
∑
x
i
\sum x_i
∑xi 的大小是
k
k
k 时,左边那项的最小值。
然后直接枚举从左边从上面来即可。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int n, m, a[51][51];
int f[51][51][5001];
bool cn[51][51][5001];
int ans;
int main() {
// freopen("path.in", "r", stdin);
// freopen("path.out", "w", stdout);
memset(f, 0x7f, sizeof(f));
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
scanf("%d", &a[i][j]);
}
cn[1][1][a[1][1]] = 1;
f[1][1][a[1][1]] = a[1][1] * a[1][1];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
if (i == 1 && j == 1) continue;
for (int k = a[i][j]; k <= 50 * (n + m - 1); k++) {
if (cn[i - 1][j][k - a[i][j]]) {
cn[i][j][k] = 1;
f[i][j][k] = min(f[i][j][k], a[i][j] * a[i][j] + f[i - 1][j][k - a[i][j]]);
}
if (cn[i][j - 1][k - a[i][j]]) {
cn[i][j][k] = 1;
f[i][j][k] = min(f[i][j][k], a[i][j] * a[i][j] + f[i][j - 1][k - a[i][j]]);
}
}
}
ans = 2e9;
for (int k = 0; k <= 50 * (n + m - 1); k++)
if (cn[n][m][k])
ans = min(ans, (n + m - 1) * f[n][m][k] - k * k);
printf("%d", ans);
return 0;
}