如何计算元素的所有可能子集与重复元素的总数

时间:2019-07-11 15:48:21

标签: algorithm dynamic-programming

我给了一些数字{a,b,c,d},我可以多次使用这些数字来使总和为'N'。我必须计算所有可能的组合,假设它为“ A”,并且我必须返回A%M,其中M是一些大质数。 对我造成问题的约束是N <= 10 ^ 18。集{a,b,c,d ..}的大小小于等于100。

我正在使用动态编程来解决它,但问题是我无法使用10 ^ 18大小的数组,如果我不缓存预先计算的值,则会超出时间限制。

    #define M 1000000007
    long long solve(long long N, vector<long long>& v, vector<long long>& dp){
        if(N==0){
           return 1;
         }
         if(dp[N]!=-1){
               return dp[N];  
          }
         int n = v.size(); // maximum size of v <=100
         long long ans = 0;
         for(int i=0;i<n;i++){
                 if(N-v[i]>=0){
                     ans = (ans + solve(N-v[i],v,dp))%M; 
                  }
            }
          dp[N] = ans;
          return ans;

     }
    int main(){

        long long n, N;   // n: size of set , N: expected sum
        cin>>n>>N;
        vector<long long>v(n);
        for(int i=0;i<n;i++){
             cin>>v[i];
         }
        long long ans  = 0;
        vector<long long>dp(N+1,-1);
        for(int i = 0;i<n;i++){
                if(N-v[i]>=0){
                     ans = (ans + solve(N-v[i],v,dp))%M; 
                  }
           }
        cout<<ans<<endl;
     }

如何对其进行优化以处理10 ^ 18的总和而不会浪费时间。

1 个答案:

答案 0 :(得分:2)

为了举例说明,我假设您的集合是{1, 1, 3},并且我们要计算第100项。但是这种方法通常会起作用。

k是您设置的最大值。让s[i]为使i求和的方式数量。我们的起始条件为s[-k+1] = 0s[-k+2]= 0,...,s[-1] = 0,但s[0] = 1。我们对{a, b, c, ...}的归纳步骤是s[n] = s[n-a] + s[n-b] + s[n-c] + ...

但是现在重新构想它。考虑向量v[n] = (s[n-k+1], s[n-k+2], ... + s[n])。假设A[m]是要乘以向量v[n+m] = (s[n+m-k+1], s[n+m-k+2], ..., s[n+m])所必须乘以的矩阵。我假设它存在。如果您想算出斐波那契数列的通常证明,就可以推广。

现在,我们需要有关这些矩阵的两个事实。

第一个是A[m1 + m2] = A[m1] * A[m2]。要看到这一点,请注意,对于所有n(A[m1] * A[m2])(v[n]) = A[m1]( A[m2]( v[n] ) ) = A[m1]( v[n + m2] ) = v[n + m2 + m1] = A[m1 + m2]( v[n] )

第二个是我们可以很容易地计算A[1]。它在上对角线上全为1,然后在最后一行包含+1的行都出现在我们的集合中。 (或者,如果元素在我们的集合中两次,则为+2,因为在我们的示例中我确定是正确的。)因此对于我们的示例,我们有:

[0 1 0]   [ v[n-2] ]   [ v[n-1] ]
[0 0 1] * [ v[n-1] ] = [ v[n]   ]
[1 0 2]   [ v[n]   ]   [ v[n+1] ]

初始矩阵为A[1]

现在假设我们要计算s[100]。那是v[100]的最后一个条目。但是现在,我们将其减半,如下所示:A[100] = A[50] * A[50]A[50] = A[25] * A[25]A[25] = A[12] * A[13]A[13] = A[1] * A[12]A[12] = A[6] * A[6]A[6] = A[3] * A[3]A[3] = A[2] * A[1]。最后,A[2] = A[1] * A[1]。但是我们有A[1],这在8个矩阵相乘后得到A[100]。由于所有内容都呈指数增长,因此理论上的答案有非常大的整数,但是这些操作很容易进行mod p

这可行吗?如果n = 10**18最多有60个减半,那么用这种幼稚的方法,每个减半都可以对120个矩阵运算进行另一个+1乘法。如果集合中的最大元素为100,则表示每个矩阵乘法(半乘法,半加法)大约为200万,用于2.4亿次运算。

您还有很多工作要做。但是使用这种矩阵方法,至少是可行的。