[Hdp] lc3250. 单调数组对的数目 I、II(dp+状态表示+状态转移+前缀和优化dp+周赛410_3+周赛410_4)

1. 题目来源

链接:3250. 单调数组对的数目 I

很不错的题解:

2. 题目解析

首先 Q3 题应该要能做出来的。但这也需要对题目有一个完整的分析和理解。

状态定义:f[i][j] 已经填了 i 个数,且 arr1[i] = j 的方案数。

状态转移:

  • 已知 arr1[i]=j,那么 arr2[i] = nums[i] - j
  • f[i][j] 的转移肯定与上一个数有关。这里有限制如下:
    • arr1[i-1]<=j
    • arr2[i-1]>=nums[i]-j
  • 且,arr2[i-1]=nums[i-1]-arr1[i-1],即有:
    • nums[i-1]-arr[i-1]>=nums[i]-j
    • arr1[i-1] <= j
  • 此时,对于状态转移来讲,我们需要枚举 arr1[i-1] 的取值,假设为 k,则有:
    • nums[i-1]-k>=nums[i]-j
    • k<=j
  • 这里有点特殊的是,需要先枚举 i(位置)、再枚举 j(i 位置的取值),再枚举 k(状态转移的取值)。中间夹杂一些判断即可。

结果返回:

  • 最终的 f[n-1][j] 应该将 j = 0~nums[n-1] 这些答案全部累加起来。因为这些都是符合定义下的答案。

关于 T4 来看,需要优化掉内层的循环。f[i][j] 是由一系列 f[i-1][k] 得到的,且经过不等式的移项就可以发现:

  • 0≤k≤nums[i−1]−nums[i]+j
  • 说明 k 是一段连续的前缀
  • 则可以使用前缀和优化 dp 的思想,对dp 进行优化。
  • 且 f[i] 仅与 f[i-1] 有关,则可以使用滚动数组进行优化。

  • 时间复杂度 O ( n ∗ m 2 ) O(n*m^2) O(nm2) O ( n ∗ m ) O(n*m) O(nm)
  • 空间复杂度 O ( n m ) O(nm) O(nm)

class Solution {
public:
    int countOfPairs(vector<int>& nums) {
        int n = nums.size();
        const int MOD = 1e9+7;

        int f[n][51];
        memset(f, 0, sizeof f);
        for (int i = 0; i <= nums[0]; i ++ ) f[0][i] = 1;
        for (int i = 1; i < n; i ++ ) {
            for (int j = 0; j <= nums[i]; j ++ ) {
                int t = nums[i] - j;        // 不等式条件
                for (int k = 0; k <= j; k ++ ) {  // arr[i-1] 的取值范围
                    if (nums[i - 1] - k >= t) {
                        f[i][j] = (f[i][j] + f[i - 1][k]) % MOD;  // 取模放到括号外面哈....
                    }
                }
            }
        }

        int res = 0;
        for (int i = 0; i <= nums[n - 1]; i ++ ) res = (res + f[n - 1][i]) % MOD;
        return res;
    }
};

T4:

class Solution {
public:
    int countOfPairs(vector<int>& nums) {
        int n = nums.size();
        int mx = 0;
        for (int x : nums) mx = max(mx, x);
        const int MOD = 1e9 + 7;

        // 初始化 f[0][j] 以及对应的前缀和
        long long f[n][mx + 1], g[n][mx + 1];
        memset(f, 0, sizeof(f)); memset(g, 0, sizeof(g));
        for (int i = 0; i <= nums[0]; i++) f[0][i] = 1;
        g[0][0] = f[0][0];
        for (int i = 1; i <= mx; i++) g[0][i] = (g[0][i - 1] + f[0][i]) % MOD;

        for (int i = 1; i < n; i++) {
            // 计算单个 DP 状态
            for (int j = 0; j <= nums[i]; j++) {
                int lim = min(j, j + nums[i - 1] - nums[i]);
                if (lim >= 0) f[i][j] = g[i - 1][lim];
            }
            // 计算前缀和
            g[i][0] = f[i][0];
            for (int j = 1; j <= mx; j++) g[i][j] = (g[i][j - 1] + f[i][j]) % MOD;
        }

        return g[n - 1][mx];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值