前缀和优化dp,LeetCode 3193. 统计逆序对的数目

目录

一、题目

1、题目描述

2、接口描述

python3

cpp

C#

3、原题链接

二、解题报告

1、思路分析

2、复杂度

3、代码详解

python3

cpp

C#


一、题目

1、题目描述

给你一个整数 n 和一个二维数组 requirements ,其中 requirements[i] = [endi, cnti] 表示这个要求中的末尾下标和 逆序对 的数目。

整数数组 nums 中一个下标对 (i, j) 如果满足以下条件,那么它们被称为一个 逆序对

  • i < jnums[i] > nums[j]

请你返回 [0, 1, 2, ..., n - 1]

排列
perm 的数目,满足对 所有requirements[i] 都有 perm[0..endi] 恰好有 cnti 个逆序对。

由于答案可能会很大,将它对 109 + 7 取余 后返回。

2、接口描述

python3
复制代码
 ​
python 复制代码
class Solution:
    def numberOfPermutations(self, n: int, requirements: list[list[int]]) -> int:
 
cpp
复制代码
 ​
cpp 复制代码
class Solution {
public:
    int numberOfPermutations(int n, vector<vector<int>>& requirements) {
        
    }
};
C#
复制代码
 ​
cs 复制代码
public class Solution {
    public int NumberOfPermutations(int n, int[][] requirements) {

    }
}

3、原题链接

3193. 统计逆序对的数目


二、解题报告

1、思路分析

每个前缀都要满足限制,我们不妨考虑每个前缀最后一个位置填什么?

因为是一个排列,所以我们不关注具体的数字,只关注所填数字的rank

也就是说 我们在最右端填一个数字x,就能得到左边有几个数字和x构成逆序对

这样就可以找到子问题

定义状态 f(i, j) 为 考虑到 下标 i 为止,我们构成 j 个逆序对的方案数

ed = 当前位置的逆序对数目限制,如果没有要求就是M

那么 如果 cnt[i - 1] >= 0,那么我们当前位置应该填一个不影响 cnt[i - 1] 的数字:

复制代码
f[i][j] = f[i][j - cnt[i - 1]], 如果 cnt[i - 1] <= j <= min(i + cnt[i - 1], ed)

如果 cnt[i - 1] = -1,那么当前位置填的数字和左边的逆序对贡献只要不超过 ed 即可

复制代码
f[i][j] = sum(f[i - 1][j - k] for k in range(min(i, j) + 1)) % P

这个逻辑显然可以前缀和优化

在具体代码实现的时候我们可以滚动数组优化掉第一维

2、复杂度

时间复杂度: O(NM),M为 cnt 的值域,空间复杂度:O(NM)

3、代码详解

python3
复制代码
 ​
python 复制代码
P = 1_000_000_007

class Solution:
    def numberOfPermutations(self, n: int, requirements: list[list[int]]) -> int:
        st = [-1] * n
        st[0] = M = 0
        for end, cnt in requirements:
            st[end] = cnt
            M = max(M, cnt)
        if st[0]: return 0

        f = [0] * (M + 1)
        f[0] = 1

        for i in range(1, n):
            s = st[i - 1]
            ed = M if st[i] < 0 else st[i]
            if s >= 0:
                for j in range(M + 1):
                    f[j] = f[s] if s <= j <= min(i + s, ed) else 0
            else:
                for j in range(1, ed + 1):               
                    f[j] += f[j - 1]
                    if f[j] >= P: f[j] -= P
                for j in range(ed, i, -1):
                    f[j] = (f[j] - f[j - i - 1]) % P

        return f[st[n - 1]]
cpp
复制代码
 ​
cpp 复制代码
constexpr int P = 1'000'000'007;
class Solution {
public:
    int numberOfPermutations(int n, vector<vector<int>>& requirements) {
        int M = 0;
        std::vector<int> st(n, -1);
        st[0] = 0;
        for (auto &r : requirements) {
            int end = r[0], cnt = r[1];
            st[end] = cnt;
            M = std::max(M, cnt);
        }
        if (st[0]) {
            return 0;
        }
        std::vector<int> f(M + 1);
        f[0] = 1;
        for (int i = 1; i < n; ++ i) {
            int s = st[i - 1];
            int ed = st[i] < 0 ? M : st[i];
            if (s >= 0) {
                for (int j = 0; j <= M; ++ j) {
                    f[j] = (s <= j && j <= std::min(ed, i + s)) ? f[s] : 0;
                }
            } else {
                for (int j = 1; j <= ed; ++ j) {
                    f[j] += f[j - 1];
                    if (f[j] >= P) {
                        f[j] -= P;
                    }
                }
                for (int j = ed; j > i; -- j) {
                    f[j] = ((f[j] - f[j - i - 1]) % P + P) % P;
                }
            }
        }
        return f[st[n - 1] < 0 ? M : st[n - 1]];
    }
};
C#
复制代码
 ​
cs 复制代码
public class Solution {
    public readonly int P = 1_000_000_007;
    public int NumberOfPermutations(int n, int[][] requirements) {
        int M = 0;
        int[] st = new int[n];
        Array.Fill(st, -1);
        st[0] = 0;
        foreach(var v in requirements) {
            st[v[0]] = v[1];
            M = Math.Max(M, v[1]);
        }
        if (st[0] > 0) {
            return 0;
        }

        int[] f = new int[M + 1];
        f[0] = 1;
        for (int i = 1; i < n; ++ i) {
            int s = st[i - 1];
            int ed = st[i] < 0 ? M : st[i];
            if (s >= 0) {
                for (int j = 0; j <= M; ++ j) {
                    f[j] = s <= j && j <= Math.Min(i + s, ed) ? f[s] : 0;
                }
            } else {
                for (int j = 1; j <= ed; ++ j) {
                    f[j] += f[j - 1];
                    if (f[j] >= P) {
                        f[j] -= P;
                    }
                }
                for (int j = ed; j > i; -- j) {
                    f[j] = ((f[j] - f[j - i - 1]) % P + P) % P;
                }
            }
        }
        return f[st[n - 1] < 0 ? M : st[n - 1]];
    }
}
相关推荐
xqqxqxxq几秒前
洛谷算法1-1 模拟与高精度(NOIP经典真题解析)java(持续更新)
java·开发语言·算法
razelan1 分钟前
初级算法技巧 4
算法
砍树+c+v2 分钟前
3a 感知机训练过程示例(手算拆解,代码实现)
人工智能·算法·机器学习
zy_destiny3 分钟前
【工业场景】用YOLOv26实现4种输电线隐患检测
人工智能·深度学习·算法·yolo·机器学习·计算机视觉·输电线隐患识别
智驱力人工智能13 分钟前
货车违规变道检测 高速公路安全治理的工程实践 货车变道检测 高速公路货车违规变道抓拍系统 城市快速路货车压实线识别方案
人工智能·opencv·算法·安全·yolo·目标检测·边缘计算
罗湖老棍子16 分钟前
【例9.18】合并石子(信息学奥赛一本通- P1274)从暴搜到区间 DP:石子合并的四种写法
算法·动态规划·区间dp·区间动态规划
2301_8107301025 分钟前
python第四次作业
数据结构·python·算法
adam_life28 分钟前
区间动态# P1880 [NOI1995] 石子合并】
算法
坠金33 分钟前
递归、递归和回溯的区别
算法
恋爱绝缘体137 分钟前
Java语言提供了八种基本类型。六种数字类型【函数基数噶】
java·python·算法