【组合数学】【动态规划】【前缀和】1735生成乘积数组的方案数

简介: 【组合数学】【动态规划】【前缀和】1735生成乘积数组的方案数

作者推荐

【动态规划】【状态压缩】【2次选择】【广度搜索】1494. 并行课程 II

本文涉及知识点

动态规划汇总

C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频

组合数学

LeetCode1735 生成乘积数组的方案数

给你一个二维整数数组 queries ,其中 queries[i] = [ni, ki] 。第 i 个查询 queries[i] 要求构造长度为 ni 、每个元素都是正整数的数组,且满足所有元素的乘积为 ki ,请你找出有多少种可行的方案。由于答案可能会很大,方案数需要对 109 + 7 取余 。

请你返回一个整数数组 answer,满足 answer.length == queries.length ,其中 answer[i]是第 i 个查询的结果。

示例 1:

输入:queries = [[2,6],[5,1],[73,660]]

输出:[4,1,50734910]

解释:每个查询之间彼此独立。

[2,6]:总共有 4 种方案得到长度为 2 且乘积为 6 的数组:[1,6],[2,3],[3,2],[6,1]。

[5,1]:总共有 1 种方案得到长度为 5 且乘积为 1 的数组:[1,1,1,1,1]。

[73,660]:总共有 1050734917 种方案得到长度为 73 且乘积为 660 的数组。1050734917 对 109 + 7 取余得到 50734910 。

示例 2 :

输入:queries = [[1,1],[2,2],[3,3],[4,4],[5,5]]

输出:[1,2,3,10,5]

提示:

1 <= queries.length <= 104

1 <= ni, ki <= 104

组合数学

ki 可以表示为 a1n1a2n2

问题等效于:

  • 将n1个a1分配给ni个数。
  • 将n2个a2分配给ni个数。
  • ⋯ \cdots

214> 104。 所以n1最大取值13。

利用动态规划进行预处理

动态规划的状态表示

vCont[i][j] 表示将j个数,分配给i个位置的可能数。

动态规划的转移方程

我们假定最后一个位置选择了0到j个数。

∑ k = 0 j \sum\large_{k=0}^jk=0jvCount[i-1][j-k]

动态规划的初始值

全为1。

动态规划的填表顺序

i从1到大,j从1到i大。

代码

核心代码

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;
  }
  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;;
};
class Solution {
public:
  vector<int> waysToFillArray(vector<vector<int>>& queries) {
    vector < vector<C1097Int<>>> vCount(m_r, vector<C1097Int<>>(m_c, 1));
    for (int r = 2; r < m_r; r++)
    {
      for (int c = 1; c < m_c; c++)
      {
        vCount[r][c] = 0;
        for (int sel = 0; sel <= c; sel++)
        {
          vCount[r][c] += vCount[r - 1][c - sel];
        }
      }
    }
    vector<int> vRet;
    for ( auto v : queries)
    {
      C1097Int biRet=1;
      for (int i = 2; i * i <= v[1]; i++)
      {
        int cnt = 0;
        while (0 == v[1] % i)
        {
          v[1] /= i;
          cnt++;
        }
        biRet *= vCount[v[0]][cnt];
      }
      if (v[1] > 1)
      {
        biRet *= vCount[v[0]][1];
      }
      vRet.emplace_back(biRet.ToInt());
    }
    return vRet;
  }
  int m_r=10000+1, m_c= 14;
};
template<class T>
void Assert(const T& t1, const T& t2)
{
  assert(t1 == t2);
}
template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
  if (v1.size() != v2.size())
  {
    assert(false);
    return;
  }
  for (int i = 0; i < v1.size(); i++)
  {
    Assert(v1[i], v2[i]);
  }
}
int main()
{ 
  vector<vector<int>> queries;
  {
    Solution sln;
    queries = { {2,6},{5,1},{73,660} };
    auto res = sln.waysToFillArray(queries);
    Assert(res, vector<int> {4, 1, 50734910});
  }
  
  {
    Solution sln;
    queries = { {1,1},{2,2},{3,3},{4,4},{5,5} };
    auto res = sln.waysToFillArray(queries);
    Assert(res, vector<int> {1, 2, 3, 10, 5});
  }
}

利用前缀和优化

class Solution {
public:
  vector<int> waysToFillArray(vector<vector<int>>& queries) {   
    vector < vector<C1097Int<>>> vCount(m_r, vector<C1097Int<>>(m_c, 1));
    for (int r = 2; r < m_r; r++)
    {
      C1097Int biSum = 1;
      for (int c = 1; c < m_c; c++)
      {
        biSum += vCount[r - 1][c];
        vCount[r][c] = biSum;
      }
    }
    vector<int> vRet;
    for ( auto v : queries)
    {
      C1097Int biRet=1;
      for (int i = 2; i * i <= v[1]; i++)
      {
        int cnt = 0;
        while (0 == v[1] % i)
        {
          v[1] /= i;
          cnt++;
        }
        biRet *= vCount[v[0]][cnt];
      }
      if (v[1] > 1)
      {
        biRet *= vCount[v[0]][1];
      }
      vRet.emplace_back(biRet.ToInt());
    }
    return vRet;
  }
  int m_r=10000+1, m_c= 14;
};

2023年2月

class C1097Int

{

public:

C1097Int(int iData = 0) :m_iData(iData)

{

}
 C1097Int  operator+(const C1097Int& o)const
 {
   return C1097Int(((long long)m_iData + o.m_iData) % s_iMod);
 }
 C1097Int&  operator+=(const C1097Int& o)
 {
   m_iData = ((long long)m_iData + o.m_iData) % s_iMod;
   return *this;
 }
 C1097Int  operator*(const C1097Int& o)const
 {
   return((long long)m_iData *o.m_iData) % s_iMod;
 }
 C1097Int&  operator*=(const C1097Int& o)
 {
  m_iData =((long long)m_iData *o.m_iData) % s_iMod;
   return *this;
 }
 int ToInt()const
 {
   return m_iData;
 }

private:

int m_iData = 0;;

static const int s_iMod = 1000000007;

};

int operator+(int iData, const C1097Int& int1097)

{

int iRet = int1097.operator+(C1097Int(iData)).ToInt();

return iRet;

}

int& operator+=(int& iData, const C1097Int& int1097)

{

iData = int1097.operator+(C1097Int(iData)).ToInt();

return iData;

}

int operator*(int iData, const C1097Int& int1097)

{

int iRet = int1097.operator*(C1097Int(iData)).ToInt();

return iRet;

}

int& operator*=(int& iData, const C1097Int& int1097)

{

iData = int1097.operator*(C1097Int(iData)).ToInt();

return iData;

}

class Solution {

public:

vector waysToFillArray(vector<vector>& queries) {

m_C.assign(100 * 100 + 1, vector(15, 0));

vector vRet;

for (const auto& v : queries)

{

const int& len = v[0];

int iMul = v[1];

C1097Int may(1);

for (int i = 2; i*i <= iMul; i++)

{

if (0 == iMul%i)

{

int r = 0;

while (0 == iMul%i)

{

iMul /= i;

r++;

}

may *= C(len, r);

}

}

if (iMul > 1)

{

may *= C(len, 1);

}

vRet.push_back(may.ToInt());

}

return vRet;

}

C1097Int C(int iPos, int iNeedSel)

{

if (0 == iNeedSel)

{

return 1;

}

if (0 == iPos)

{

return 0;

}

if (0 != m_C[iPos][iNeedSel].ToInt())

{

return m_C[iPos][iNeedSel];

}

return m_C[iPos][iNeedSel] = C(iPos - 1, iNeedSel) + C(iPos, iNeedSel - 1);

}

vector<vector> m_C;

};


相关文章
|
7月前
|
算法 测试技术 C++
【动态规划】【前缀和】【数学】2338. 统计理想数组的数目
【动态规划】【前缀和】【数学】2338. 统计理想数组的数目
|
7月前
|
算法 测试技术 C++
【数位dp】【动态规划】C++算法:233.数字 1 的个数
【数位dp】【动态规划】C++算法:233.数字 1 的个数
【动态规划刷题 16】最长等差数列 (有难度) && 等差数列划分 II - 子序列
【动态规划刷题 16】最长等差数列 (有难度) && 等差数列划分 II - 子序列
101 0
|
4月前
|
算法 C++
【算法】前缀和算法——和可被K整除的子数组
【算法】前缀和算法——和可被K整除的子数组
|
4月前
|
算法
【算法】前缀和——除自身以外数组的乘积
【算法】前缀和——除自身以外数组的乘积
|
7月前
DAY-4 | 力扣 - 求自身以外数组的乘积:区间划分,左右累乘,巧求乘积
该文档是关于LeetCode上的一道题目“Product of Array Except Self”的题解。提供了两种解题方法,一是暴力破解,即计算所有数的乘积后再逐个除以当前元素;二是左右累乘法,通过两次遍历数组分别计算左侧和右侧元素的乘积,避免了除法操作。其中,左右累乘法更优,代码实现中展示了这种方法。
48 1
|
7月前
|
算法
DAY-7 | 牛客-BM21 寻找旋转数组的最小元素:二分法分治思想真的很可靠
这是一个关于编程题目的摘要:题目是牛客网上的&quot;BM21 旋转数组的最小数字&quot;,要求找到旋转数组中的最小数字。题解介绍了使用二分查找算法来解决此问题,因为其时间复杂度优于暴力搜索的线性时间复杂度。二分查找的核心是通过比较中间元素与右端元素的大小,不断缩小搜索范围,最终找到最小值。代码示例展示了如何实现这个算法。总结中强调了二分查找适用于部分有序数组,并指出了解决这类问题的关键在于理解数组的二段单调性。
47 1
|
7月前
|
算法 测试技术 C++
【动态规划 区间dp 位运算】3117. 划分数组得到最小的值之和
【动态规划 区间dp 位运算】3117. 划分数组得到最小的值之和
【动态规划 区间dp 位运算】3117. 划分数组得到最小的值之和
|
7月前
|
算法
简记二分算法模板与代码案例:整数二分和浮点数二分
本文介绍了两种算法模板,分别是整数二分和浮点数二分。
57 0
|
7月前
|
人工智能 算法
前缀和算法题(区间次方和、小蓝平衡和、大石头的搬运工、最大数组和)
前缀和算法题(区间次方和、小蓝平衡和、大石头的搬运工、最大数组和)