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

时间:2018-01-08 11:02:08

标签: c++ algorithm sorting math dynamic-programming

问题在于:

  

给定 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;
}

3 个答案:

答案 0 :(得分:2)

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

#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(N 2 )时间。

答案 1 :(得分:1)

让我们将子集划分为&#34;代和#34;,其中每个新一代与下一个不同的子集具有一个更长的值。

显然,第一代由仅由一个单一数字组成的子集组成。只需迭代数组就可以得到它们。

从每一代开始,通过向每个子集添加当前子集中最后一个数字后面的每个数字(将此索引与您的子集一起存储!)来获得下一个子集,但仅当相关数量大于子集中的最后一个。每当您发现这样一个新子集时,都会增加一个计数器。

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

有趣的是,空子集不被认为是

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

答案 2 :(得分:0)

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

假设传递的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。