当前位置: 首页 > news >正文

【C++动态规划 组合数学】3193. 统计逆序对的数目|2266

本文涉及知识点

C++动态规划
组合数学汇总 离散数学
C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频

LeetCode3193. 统计逆序对的数目

给你一个整数 n 和一个二维数组 requirements ,其中 requirements[i] = [endi, cnti] 表示这个要求中的末尾下标和 逆序对 的数目。
整数数组 nums 中一个下标对 (i, j) 如果满足以下条件,那么它们被称为一个 逆序对 :
i < j 且 nums[i] > nums[j]
请你返回 [0, 1, 2, …, n - 1] 的
排列
perm 的数目,满足对 所有 的 requirements[i] 都满足 perm[0…endi] 中恰好有 cnti 个逆序对。
由于答案可能会很大,将它对 109 + 7 取余 后返回。
示例 1:
输入:n = 3, requirements = [[2,2],[0,0]]
输出:2
解释:
两个排列为:
[2, 0, 1]
前缀 [2, 0, 1] 的逆序对为 (0, 1) 和 (0, 2) 。
前缀 [2] 的逆序对数目为 0 个。
[1, 2, 0]
前缀 [1, 2, 0] 的逆序对为 (0, 2) 和 (1, 2) 。
前缀 [1] 的逆序对数目为 0 个。
示例 2:
输入:n = 3, requirements = [[2,2],[1,1],[0,0]]
输出:1
解释:
唯一满足要求的排列是 [2, 0, 1] :
前缀 [2, 0, 1] 的逆序对为 (0, 1) 和 (0, 2) 。
前缀 [2, 0] 的逆序对为 (0, 1) 。
前缀 [2] 的逆序对数目为 0 。
示例 3:
输入:n = 2, requirements = [[0,0],[1,0]]
输出:1
解释:
唯一满足要求的排列为 [0, 1] :
前缀 [0] 的逆序对数目为 0 。
前缀 [0, 1] 的逆序对为 (0, 1) 。
提示:
2 <= n <= 300
1 <= requirements.length <= n
requirements[i] = [endi, cnti]
0 <= endi <= n - 1
0 <= cnti <= 400
输入保证至少有一个 i 满足 endi == n - 1 。
输入保证所有的 endi 互不相同。

经典问题(动态规划+组合数学+前缀和)

动态规划的状态表示

dp[i][j] 表示 [0,i]排列中,逆序对数量为j的组合的数量。i ∈ \in [0,n-1] j ∈ \in [0,m] ,m= max(cnti)
cur = dp[i] pre= dp[i-1]

动态规划的填表顺序

i 从1到n-1,枚举后续状态

动态规划的转移方程

dp[0] = pre[0] 将i插到最后。
dp[1] = pre[0]+pre[1] 将i插到 最后和倒数第一
dp[2] = pre[0]+pre[1] + pre[2] 将i插到 最后和倒数第一 倒数第二
⋮ \vdots
dp[j] = pre[j-i…j]

动态规划的初始值

pre[0]=1 ,其它全为0。

动态规划的返回值

本题改进

令 N = endi+1
如果i 等于任意endi,则计算完dp[i] 将出dp[i][cnti]外的dp[i]全部设置为0。
从n中选取N个数,共 C n N C_{n}^{N} CnN中选择方式,离散化后就是dp[N][N对应的cnti]。 答案就是两者相乘。

代码

核心代码

template<int MOD = 1000000007>
class C1097Int
{
public:C1097Int(long long llData = 0) :m_iData(llData% MOD){}C1097Int  operator+(const C1097Int& o)const{return C1097Int(((long long)m_iData + o.m_iData) % MOD);}C1097Int& operator+=(const C1097Int& o){m_iData = ((long long)m_iData + o.m_iData) % MOD;return *this;}C1097Int& operator-=(const C1097Int& o){m_iData = (m_iData + MOD - o.m_iData) % MOD;return *this;}C1097Int  operator-(const C1097Int& o){return C1097Int((m_iData + MOD - o.m_iData) % MOD);}C1097Int  operator*(const C1097Int& o)const{return((long long)m_iData * o.m_iData) % MOD;}C1097Int& operator*=(const C1097Int& o){m_iData = ((long long)m_iData * o.m_iData) % MOD;return *this;}C1097Int  operator/(const C1097Int& o)const{return *this * o.PowNegative1();}C1097Int& operator/=(const C1097Int& o){*this /= o.PowNegative1();return *this;}bool operator==(const C1097Int& o)const{return m_iData == o.m_iData;}bool operator<(const C1097Int& o)const{return m_iData < o.m_iData;}C1097Int pow(long long n)const{C1097Int iRet = 1, iCur = *this;while (n){if (n & 1){iRet *= iCur;}iCur *= iCur;n >>= 1;}return iRet;}C1097Int PowNegative1()const{return pow(MOD - 2);}int ToInt()const{return m_iData;}
private:int m_iData = 0;;
};template<class Result = C1097Int<> >
class CCombination
{
public:CCombination(){m_v.assign(1, vector<Result>(1,1));}Result Get(int sel, int total){assert(sel <= total);while (m_v.size() <= total){int iSize = m_v.size();m_v.emplace_back(iSize + 1, 1);for (int i = 1; i < iSize; i++){m_v[iSize][i] = m_v[iSize - 1][i] + m_v[iSize - 1][i - 1];}}return m_v[total][sel];}
protected:vector<vector<Result>> m_v;
};class Solution {public:int numberOfPermutations(int n , vector<vector<int>>& requirements) {int  M = 0;int N = 0;for (const auto& v : requirements) {N = max(N, v[0] + 1);M = max(M, v[1]);}sort(requirements.begin(), requirements.end(),greater<>());vector<vector<C1097Int<>>> dp(N, vector < C1097Int<>>(M + 1));dp[0][0] = 1;auto Clear = [&](int i ) {if (requirements.size() && (requirements.back()[0] == i)) {for (int j = 0; j <= M; j++) {if (j == requirements.back()[1]) { continue; }dp[i][j] = 0;}requirements.pop_back();}};Clear(0);for (int i = 1; i < N; i++) {auto& pre = dp[i - 1];auto& cur = dp[i];C1097Int<> sum = 0;for (int j = 0; j <= M; j++) {sum += pre[j];const int i1 = j - i-1;if(i1 >= 0 ){ sum -= pre[i1]; }						cur[j] = sum;}Clear(i);}CCombination com;auto ans = accumulate(dp.back().begin(),dp.back().end(),C1097Int<>())* com.Get(N, n);return ans.ToInt();}};

单元测试

vector<vector<int>> requirements;int n;TEST_METHOD(TestMethod11){n = 3, requirements = { {2,2},{0,0} };auto res = Solution().numberOfPermutations(n, requirements);AssertEx(2, res);}TEST_METHOD(TestMethod12){n = 3, requirements = { {2,2},{1,1},{0,0} }			;auto res = Solution().numberOfPermutations(n, requirements);AssertEx(1, res);}TEST_METHOD(TestMethod13){n = 2, requirements = { {0,0},{1,0} };auto res = Solution().numberOfPermutations(n, requirements);AssertEx(1, res);}

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

相关文章:

  • 精通MySQL:快速获取数据表字段提高开发效率
  • 二级建造师备考攻略:高效学习与题库推荐
  • WPF View 与ViewModel注入对象
  • MySQL MVCC工作流程详解
  • 《重构全球贸易体系用户指南》解读
  • 有限元分析如何应对很薄的零件?
  • C++数组
  • 美信监控易:揭秘高效数据采集和数据分析双引擎
  • 领域驱动设计的创新和佛学(01)
  • 每日一题洛谷P1014 [NOIP 1999 普及组] Cantor 表c++
  • 使用 Kubernetes Scheduler Framework 插件机制实现 Filter 插件的最小可运行 Demo
  • SparseDrive---论文阅读
  • 【HDFS入门】数据存储原理全解,从分块到复制的完整流程剖析
  • 线程安全学习
  • Python项目--基于Python的自然语言处理文本摘要系统
  • C++面试考点:类(class)
  • 【开源项目】Excel手撕AI算法深入理解(四):AlphaFold、Autoencoder
  • MySQL 锁机制全景图:分类、粒度与示例一图掌握
  • 每天记录一道Java面试题---day39
  • Web自动化测试的详细流程和步骤
  • 上海建站网站的企业/深圳做网站
  • 做网站可以找设计公司吗/百度招聘官网首页
  • h5网站页面/网络站点推广的方法
  • 爱美刻在线制作网站/百度收录规则
  • 宁夏建设教育协会网站/app推广赚钱
  • 旅游景点网站设计/引流平台有哪些