写这篇的起因还是因为 2021.7.10 力扣双周赛 的第四题用 记忆化 + 深搜 来写的,但是一直有几个样例过不去,在困扰之际看到了另一篇的题解 为什么记忆化搜索得不到正解? 中才恍然大悟,特此做一个记录
无后效性。所谓的无后效性,即
对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策。
题目描述 :
比赛思路:
用一个邻接表来表示图,g[i] = {p0,...,p_j} 与 点 i 相邻的点为 {p0,...,p_j}
gg[i][j] 来表示 点 i 和 点 j 之间的时间开销
dp[curp][curtime] 在点 curp ,且已经用时 curtime 的情况下,到达终点的最小开销
则 : dp[0][0] = max{ dp[j][gg[0][j] } 往下一直深搜即可
看上去好像没有什么问题,但是 :
对于这样这种 a - > d 的深搜
(1)从a出发访问b,接下来访问c和d,实际访问路径为a->b->d和a->b->c->d.
(2)从a出发访问c,直接返回memo[c],实际访问路径为a->c->d.
不难发现,访问路径缺少了a->c->b->d,为什么会这样呢?
原因在于(1)中,从b访问c时计算的memo[c]并非是c到d的最大概率。c到d共有c->d和c->b->d两条路径,由于b会被标记为已访问,因而无法从c到b,所以无法保证memo[c]的正确性。更进一步,上述过程并不满足动态规划中的无后效性。所谓的无后效性,即对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策。
所以即使是我设的是 dp[curp][curtime] 两个维度,若有两条路到 curp 这个点的时候 用时都是 curtime ,比如上图中的 c 这个点,那么该点的 c 并不一定是最小的,因为有前驱的值来限制它,所以这种方法的正确性并不能保证,即 动态规划的后效性无法保证
比赛时的代码:
#define x first
#define y second
#include <iostream>
#include <bits/stdc++.h>
#include <ctime>
#include <algorithm>
#include <stdlib.h>
using namespace::std;
using LL = long long;
using PII = pair<int,int>;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int dir[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
class Solution {
public:
vector<vector<int>> g;
int n;
int dp[1005][1005];// dp[i] i 到 n - 1 的最小花费 - 1 表示不可达
int maxTime;
bool vi[1005];
int gg[1005][1005];
int dfs(int curtime,int curp,vector<int>& pf) {
if(dp[curp][curtime] != INF) {
return dp[curp][curtime];
}// 若之前已经求过
if(curp == n - 1) {
return pf[n - 1];
}
int res = INF;
for(auto& next : g[curp]) {
if(!vi[next]) {
if(curtime + gg[curp][next] > maxTime) continue;
vi[next] = true;
int t = dfs(curtime + gg[curp][next],next,pf);
if(t != -1)
res = min(res,t);
vi[next] = false;
}
}
if(res != INF)
res += pf[curp];
dp[curp][curtime] = res == INF ? -1 : res;
return dp[curp][curtime];
}
int minCost(int maxTime_, vector<vector<int>>& edges, vector<int>& pFees) {
n = pFees.size();
g.resize(n);
maxTime = maxTime_;
// 初始化
for(int i = 0;i < n;i ++) {
memset(dp[i],INF,4 * 1005);
vi[i] = false;
memset(gg[i],-1,4 * n);
}
for(auto& e : edges) {
if(gg[e[0]][e[1]] == -1) {
g[e[0]].push_back(e[1]);
g[e[1]].push_back(e[0]);
gg[e[0]][e[1]] = e[2];
gg[e[1]][e[0]] = e[2];
} else {
gg[e[0]][e[1]] = min(gg[e[0]][e[1]],e[2]);
gg[e[1]][e[0]] = gg[e[0]][e[1]];
}
}
vi[0] = true;
int t = dfs(0,0,pFees);
return t;
}
};
正确代码 :
// author : wyx
#define x first
#define y second
#include <iostream>
#include <bits/stdc++.h>
#include <ctime>
#include <algorithm>
#include <stdlib.h>
using namespace::std;
using LL = long long;
using PII = pair<int,int>;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int dir[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
class Solution {
public:
vector<vector<int>> g;// 邻接表
int n;// 点的个数
int dp[1005][1005];// - 1 表示不可达
// dp[i][j] 在 j 这个点,总用时为 i 的时候所产生的最小开销
int gg[1005][1005];// 两个点的距离
int minCost(int maxTime, vector<vector<int>>& edges, vector<int>& ps) {
n = ps.size();
g.resize(n);
// 初始化
for(int i = 0;i < n;i ++) {
memset(dp[i],-1,4 * 1005);
memset(gg[i],-1,4 * n);
}
// 初始化图
for(auto& e : edges) {
if(gg[e[0]][e[1]] == -1) {
g[e[0]].push_back(e[1]);
g[e[1]].push_back(e[0]);
gg[e[0]][e[1]] = e[2];
gg[e[1]][e[0]] = e[2];
} else {
gg[e[0]][e[1]] = min(gg[e[0]][e[1]],e[2]);
gg[e[1]][e[0]] = gg[e[0]][e[1]];
}
}
for(int i = 0;i <= maxTime;i ++) dp[0][i] = ps[0];
for(int t = 0;t <= maxTime;t ++) {
for(int j = 0;j < n;j ++) {
if(dp[j][t] != -1) {
// 说明在时间 t 能到达 点 j 然后从 j 开始转移
for(auto& next : g[j]) {
if(gg[next][j] + t > maxTime) continue;
if(dp[next][t + gg[next][j]] == -1 || \
dp[next][t + gg[next][j]] > dp[j][t] + ps[next]) {
dp[next][t + gg[next][j]] = dp[j][t] + ps[next];
}
}
}
}
}
return dp[n - 1][maxTime];
}
};
比赛总结:
对于动态规划还是要多理解,对于无特效性也还是要多做理解,这样才能更好地理解和运用动态规划 :D