0%

P1005

二维dp的回溯以及四维dp

P1005 NOIP 2007 提高组] 矩阵取数游戏 - 洛谷

这题我一开始用两次二维dp解,每次取最优路(类似贪心)。可是我不会dp回溯,于是有了以下题解:

二维dp的回溯

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
void back(int x, int y) {

if (x == 1 && y == 1) {
return;
}

if (x > 1 && dp[x - 1][y] + a[x][y] == dp[x][y]) {
back(x - 1, y);
}
else if (y > 1 && dp[x][y - 1] + a[x][y] == dp[x][y]) {
back(x, y - 1);
}
}

很好懂吧,因为每次dp取得都是到当前格的最大值,因此路径一定存在,一步步减回去就行。

完整码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include<bits/stdc++.h>
using ll = long long;
using namespace std;

const int N=1e9+5;

int dp1[11][11], dp2[11][11];
int a[11][11];

void back(int x, int y) {

if (x == 1 && y == 1) {
a[x][y] = 0;
return;
}

if (x > 1 && dp1[x - 1][y] + a[x][y] == dp1[x][y]) {
a[x][y] = 0;
back(x - 1, y);
}
else if (y > 1 && dp1[x][y - 1] + a[x][y] == dp1[x][y]) {
a[x][y] = 0;
back(x, y - 1);
}
}

void solve(){

int n; cin >> n;
while(1) {
int x, y, z;
cin >> x >> y >> z;
if (x == 0 && y == 0 && z == 0) {
break;
}
else {
a[x][y] = z;
}
}

int sum = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
dp1[i][j] = max(dp1[i - 1][j], dp1[i][j - 1]) + a[i][j];
}
}

sum += dp1[n][n];
back(n, n);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
dp2[i][j] = max(dp2[i - 1][j], dp2[i][j - 1]) + a[i][j];
}
}

sum += dp2[n][n];
cout << sum << endl;

}

int main(){

ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int _=1;
while(_--)
solve();
return 0;

}

注意两点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void back(int x, int y) {

// a[x][y] = 0; 不要写在这里,a[x][y]后面会用到,这里不要先改,到if后面再改 !!!

if (x == 1 && y == 1) {
a[x][y] = 0;
return;
}

if (x > 1 && dp1[x - 1][y] + a[x][y] == dp1[x][y]) { // x > 1 不要越界
a[x][y] = 0;
back(x - 1, y);
}
else if (y > 1 && dp1[x][y - 1] + a[x][y] == dp1[x][y]) { // 同理, y > 1 不要越界
a[x][y] = 0;
back(x, y - 1);
}
}

果然pa了,因为当前最优不代表全局最优,贪心思路不可行,反例:

7
1 2 2
1 3 3
2 2 3
3 2 3
5 4 4
6 4 4
7 2 2
7 4 4
0 0 0

1
2
3
4
5
6
7
0 2 3 0 0 0 0
0 3 0 0 0 0 0
0 3 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 4 0 0 0
0 0 0 4 0 0 0
0 2 0 4 0 0 0

答案是25,然而我的解法第一次是:

1
2
3
4
5
6
7
0 0 3 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 2 0 0 0 0 0

得20,第二次得23。

实际上是第一次:

1
2
3
4
5
6
7
0 0 3 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
0 0 0 4 0 0 0
0 0 0 4 0 0 0
0 0 0 0 0 0 0

第二次全吞完,得25。

所以得用四维dp做。

四维dp

直接上代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<bits/stdc++.h>
using ll = long long;
using namespace std;

int dp[11][11][11][11]; // dp[i][j][p][q]表示第一遍走到点(i, j),第二遍走到点(p, q)的最优解
int a[11][11];

void solve(){

int n; cin >> n;
while(1) {
int x, y, z;
cin >> x >> y >> z;
if (x == 0 && y == 0 && z == 0) {
break;
}
else {
a[x][y] = z;
}
}

for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
for (int p = 1; p <= n; ++p) {
for (int q = 1; q <= n; ++q) {
dp[i][j][p][q] = max(max(dp[i-1][j][p-1][q], dp[i-1][j][p][q-1]), max(dp[i][j-1][p-1][q], dp[i][j-1][p][q-1])) + a[i][j] + a[p][q];
if (i == p && j == q) dp[i][j][p][q] -= a[i][j];
}
}
}
}

cout << dp[n][n][n][n] << endl;

}

int main(){

ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int _=1;
while(_--)
solve();
return 0;

}