动态规划:计算集合中存在多少个升序子集

Dynamic Programming: Count how many ascending subsets exists in a set

本文关键字:多少 升序 子集 存在 计算 集合 动态规划      更新时间:2023-10-16

这是问题所在:

给定一个整数 n和一个由n个整数组成的数组v,计算这些数字可以形成多少个升序子集。

有一些限制:

  • 1 ≤ n ≤ 300
  • v[i] ≤ 1.000.000,无论 1 ≤ i ≤ n
  • S ≤ 10^18

例如,下面是一个示例:

Input :
6
5 2 6 1 1 8
Output:
15

说明:有 15 个升序子集。 {5}, {2}, {6}, {1}, {1}, {8}

, {5, 6}, {5, 8}, {2, 6}, {2, 8}, {6, 8}, {1, 8}, {1, 8}, {5, 6, 8}, {2, 6, 8}.我把这个问题作为家庭作业。我已经搜索了堆栈溢出,数学堆栈等,但我找不到任何想法。

如果您能给我任何有关如何解决此问题的提示,那将非常有帮助。

编辑: 所以我想出了这个解决方案,但显然它在某个地方溢出了?你能帮我吗

#include <iostream>
#include <fstream>
#include <queue>
using namespace std;
ifstream fin("nrsubsircresc.in");
ofstream fout("nrsubsircresc.out");
int v[301];
int n;
long long s;
queue <int> Coada;
int main()
{
fin >> n;
for(int i = 0; i < n; i++)
{
fin >> v[i]; // reading the array
s++;
Coada.push(i);
}
while(!Coada.empty()) // if the queue is not empty
{
for(int k = Coada.front() + 1; k < n; k++) //
if( v[k] > v[Coada.front()] )
{
s++;
Coada.push(k);
}
Coada.pop();
}
fout << s;
return 0;
}

我在这里实现了贪婪的方法:

#include <algorithm>
#include <vector>
#include <array>
using namespace std;
using ll = long long;
const int N = 6;
array<int, N> numbers = { 5, 2, 6, 1, 1, 8 }; // provided data
vector<ll> seqends(N);
int main() {
for (int i = 0; i < N; ++i) {
// when we adding new number to subarray so far,
// we add at least one ascending sequence, which consists of this number
seqends[i] = 1;
// next we iterate via all previous numbers and see if number is less than the last one,
// we add number of sequences which end at this number to number of sequences which end at the last number
for (int j = 0; j < i; ++j) {
if (numbers[j] < numbers[i]) seqends[i] += seqends[j];
}
}
// Finally we sum over all possible ends
ll res = accumulate(seqends.cbegin(), seqends.cend(), (ll)0);
cout << res;
}

该算法需要 O(N) 空间和 O(N2) 时间。

让我们将子集划分为"世代",其中每一代新集与下一代的不同之处在于子集的长度多一个值。

显然,第一代是由仅由一个数字组成的子集形成的。您可以通过简单地迭代数字数组来获得它们。

从每一代开始,您可以通过将当前子集中最后一个数字之后的每个数字添加到每个子集来获得下一个数字(将此索引与您的子集一起存储!),但前提是所讨论的数字大于子集中的最后一个数字。每当发现此类新子集时,都会递增计数器。

如果你发现新一代是空的,你就完蛋了。

有趣的是,不考虑空子集???

编辑警告:最坏的情况是数字的排序序列 - 在这种情况下,每一代中的子集数量将遵循帕斯卡三角形的模式,该模式计算为二项式系数!因此,最大的一代,给定的 300 个元素,将是300!/150!个子集,每个子集拥有 150 个值,这远远超出了我们在内存中可以容纳的值!

我将通过从给定的每个元素开始添加子集来解决这个问题。这最终将成为一个动态编程问题,因为:

假设传递的array v {5,2,6,1,1,8}中有 6 个元素。然后当我们计算从2nd element '2'开始的子集时,我们可以对从第 3、第 4 或下一个元素开始的升序子集的数量使用解。

我们对递归 dp 函数的定义是:

int dp(int index, int lastElementInCurrentSubset){}

答案将是:

dp(0,v[0])+dp(1,v[1])+...dp(len,v[len-1]) //returning the number of ascending subsets 
taking nth element in it each time

因此,每次调用都会给出索引的开始位置,对于比索引前面的每个数字,我们可以选择是采用它(如果它遵循升序条件)还是离开它。我们采用这两种可能性并返回两者的总和。

if(v[index+1]>lastElementInCurrentSubset){ //check array limits for index+1
ret+=dp(index+1, v[index+1]);
}
ret+=dp(index+1, lastElementInCurrentSubset);
mem[index][lastElementInCurrentSubset]=ret; //can be used when same func params come again which will happen

基本条件将是当索引达到数组 v 的长度时,检查这是否是有效的子集并相应地返回 0/1。