题目传送门

一、状态机版本

我们把\(f\)数组定为二维的,即\(f[i][j]\)

我们用数组储存两种情况:偷与不偷。

\(f[i][0]\) 代表的是不偷第\(i\)家店铺能得到的最多现金数量;

\(f[i][1]\) 代表的是偷第\(i\)家店铺能得到的最多现金数量。

则就会出现三种情况:

AcWing 1049. 大盗阿福

解释:

图中红色的线是可行方案,你可以不抢第\(i−1\)家,也不抢第\(i\)家;

你可以不抢第\(i−1\)家,但抢第\(i\)家。

你可以抢第\(i−1\)家,但不抢第\(i\)家;

那么我们就可以得出状态转移方程了:

\(f[i][0] = max(f[i - 1][0], f[i - 1][1]);\)

\(f[i][1] = f[i - 1][0] + w[i];\)

#include <bits/stdc++.h>

using namespace std;
const int N = 1e5 + 10;
int T;          //T组数据
int n;          //每一组数据的个数n
int w[N];       //每个商店的金钱数量
/**
f[i][0]  代表的是不偷第i家店铺能得到的最多现金数量;
f[i][1]  代表的是偷第i家店铺能得到的最多现金数量。
 */
int f[N][2];

//状态机解法
int main() {
    cin >> T;
    while (T--) {
        cin >> n;
        for (int i = 1; i <= n; i++) cin >> w[i];
        //逐个把商店加入
        for (int i = 1; i <= n; i++) {
            //不偷i号商店,获利取原来前面i-1号商店决策完的最大值
            f[i][0] = max(f[i - 1][0], f[i - 1][1]);
            //偷i号商店,获利取不偷i-1号商店的决策值,再加上当前商店的金额
            f[i][1] = f[i - 1][0] + w[i];
        }
        //最终的结果二选一
        printf("%d\n", max(f[n][0], f[n][1]));
    }
    return 0;
}

二、线性DP解法

我们可以定义一个数组,为\(f[i]\)

\(f[i]\) 表示抢劫前\(i\)家能得到的最多现金数量。

那么我们前\(i\)家的抢劫结果就有两种情况:

第一种情况:不偷第\(i\)家店铺

那么\(f[i]=f[i−1]\);

第二种情况:偷第\(i\)家店铺

那么\(f[i]=f[i−2]+w[i]\)

(\(w[i]\) 表示第\(i\)家店铺总共的现金)

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;
int n;
int f[N];//DP数组,抢前i个店铺可以获取到的最大价值是多少
int w[N];//抢劫第i个店铺可以获取到的利益w[i]
//线性DP解法
int main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n;
        for (int i = 1; i <= n; i++)cin >> w[i];
        //base case
        f[0] = 0;       //还没开始抢,那么利益必须是0
        f[1] = w[1];    //抢了第一个,只能是利益为w[1]
        //从第二个开始,有递推关系存在,以抢与不抢第i个为理由进行分类
        for (int i = 2; i <= n; i++)
            //f[i-1]表示不抢第i个,那么利益就是f[i-1]
            //如果抢了第i个,那么获取到w[i]的利益,同时,前面的只能依赖于f[i-2]
            //max表示决策,到底抢两个中的哪个合适呢?
            f[i] = max(f[i - 1], f[i - 2] + w[i]);
        //输出结果
        printf("%d\n", f[n]);
    }
    return 0;
}

三、深搜+记忆化版本

#include <bits/stdc++.h>

using namespace std;
const int N = 1e5 + 10;

int a[N]; //原始数组
int f[N]; //递推结果数组
/*
记忆化搜索
状态转移方程是 前 i 个数 = max(前 i - 1 个数, 前 i - 2 个数 + 第 i 个数)
平时写一下搜索还是挺好的。 (比Dp还快 2ms)
O(n)
*/
int dfs(int u) {
    if (u <= 0) return 0;//注意一下这个边界
    if (f[u]) return f[u];//记忆化搜索的灵魂
    return f[u] = max(dfs(u - 1), dfs(u - 2) + a[u]);
}

int main() {
    int T, n;
    cin >> T;
    while (T--) {
        cin >> n;
        //每次重置一下结果数组
        memset(f, 0, sizeof f);
        for (int i = 1; i <= n; i++) cin >> a[i];
        printf("%d\n", dfs(n));
    }
    return 0;
}

四、状态机+滚动数组优化空间

#include <bits/stdc++.h>

using namespace std;
const int N = 1e5 + 10;

int n;
int a[N];
int f[2][2];//压缩一维空间到2,是不是有点过份了~
//空间仅有其它算法的 20/1
//滚动数组优化

int main() {
    int T = 1;
    cin >> T;
    while (T--) {
        //base case
        memset(f, -0x3f, sizeof f);
        f[0][0] = 0;

        cin >> n;
        for (int i = 1; i <= n; i++) cin >> a[i];

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

        cout << max(f[n & 1][0], f[n & 1][1]) << endl;
    }
    return 0;
}

五、状态机的记忆化搜索

#include <bits/stdc++.h>

using namespace std;

const int N = 1e5 + 10;
/**
 (状态机的记忆化搜索) O(n)
 */
int T, n;

int a[N];
int f[N][2];

int dfs(int u, int state) {
    if (u <= 0) return 0;
    if (f[u][state]) return f[u][state];

    if (!state) return f[u][0] = max(dfs(u - 1, 0), dfs(u - 1, 1));
    else return f[u][1] = dfs(u - 1, 0) + a[u];
}

int main() {
    cin >> T;
    while (T--) {
        cin >> n;
        for (int i = 1; i <= n; i++)cin >> a[i];
        memset(f, 0, sizeof f);
        int x = dfs(n, 0);

        memset(f, 0, sizeof f);
        int y = dfs(n, 1);
        printf("%d\n", max(x, y));
    }
    return 0;
}

相关文章:

  • 2021-06-27
  • 2022-12-23
  • 2021-12-31
  • 2022-12-23
  • 2021-06-22
  • 2021-06-25
  • 2022-02-25
猜你喜欢
  • 2021-07-19
  • 2021-07-07
  • 2022-12-23
  • 2021-05-20
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案