动态规划的无后效性

写这篇的起因还是因为 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

 

上一篇:单向链表的排序(使用冒泡排序交换节点)


下一篇:动画:面试如何轻松反转链表?