本文涉及知识点
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++**实现。