降低子序列问题的复杂度,从指数到多项式?

时间:2018-11-11 02:57:41

标签: c++ memoization subset-sum

我正在研究the following problem

  

给出一组非负的不同整数和一个值m,确定给定集合的子集是否存在被m整除的和。

     

输入:输入的第一行包含一个整数T,它表示测试用例的数量。然后是T测试用例。每个测试用例的第一行包含一个整数N和M,其中N表示数组的大小,M是我们必须检查其除数的数字。每个测试用例的第二行包含N个由空格分隔的整数,它们表示数组A []的元素。

     

输出:如果存在可被M整除的子集,则打印'1',否则打印'0'。

我尝试了递归解决方案:

#include <iostream>
#include<unordered_map>
using namespace std;

bool find_it(int a[],int &m,int n,int sum) {
    if ((sum%m)==0 && sum>0)
        return true;
    if (n==0)
        return false;
    return find_it(a,m,n-1,sum) || find_it(a,m,n-1,sum-a[n-1]);
}

int main() {
    int tc;
    cin >> tc;
    while (tc--) {
        int n,m;
        cin >> n >> m;
        int a[n];
        int sum = 0;
        for (int i=0;i<n;i++) {
            cin >> a[i];
            sum += a[i];
        }
        bool answer = find_it(a,m,n,sum);
        cout << answer << "\n";
    }
   return 0;
}

哪种方法可以正常工作并被接受,但是后来我尝试了自上而下的方法,并获得了TLE(“超过时间限制”)。我在此记忆中做错什么了?

#include <iostream>
#include<unordered_map>
using namespace std;

bool find_it(
    int a[], int &m, int n, int sum,
    unordered_map<int,unordered_map<int,bool>> &value,
    unordered_map<int,unordered_map<int,bool>> &visited){

    if ((sum%m)==0 && sum>0)
        return true;
    if(n==0)
        return false;

    if(visited[n][sum]==true)
        return value[n][sum];

    bool first = false,second = false;
    first = find_it(a,m,n-1,su1m,value,visited);

    if(sum<a[n-1])
    {
        second=false;
    }
    else
    second = find_it(a,m,n-1,sum-a[n-1],value,visited);

    visited[n][sum] = true;
    value[n][sum] = first || second;
    return value[n][sum];
}

int main() {
    int tc;
    cin >> tc;
    while (tc--) {
        int n,m;
        cin >> n >> m;
        int a[n];
        int sum = 0;
        for (int i=0;i<n;i++) {
            cin >> a[i];
            sum+=a[i];
        }
        unordered_map<int,unordered_map<int,bool>> value;
        unordered_map<int,unordered_map<int,bool>> visited;
        cout << find_it(a,m,n,sum,value,visited) << "\n";
    }
    return 0;
}

2 个答案:

答案 0 :(得分:1)

不需要value。找到有效组合后,即如果find_it返回true,则可以在所有递归调用中立即返回true。

一些补充说明:

  1. 您应该使用一致的缩进。
  2. int a[n]中的可变大小数组不是标准C ++,因此不适用于所有编译器。
  3. 没有理由将m而不是int&作为int传递。
  4. 采用布尔值的mapset相同,其中假定元素在集合中映射到true,如果在集合中则映射到false不。考虑使用unordered_set代替unordered_map
  5. 像这样组成两个unordered_map很昂贵。您可以轻松地将两个密钥都放入std::pair中并将其用作密钥。这样可以避免维护地图的开销。
  6. bits/stdc++.h也是非标准的,您应该指定正确的头文件,例如#include <unordered_map>#include <iostream>
  7. 即使模板参数中的>允许它正确解析,也应该在变量类型和它的名称之间放置空格。它使代码难以阅读。

答案 1 :(得分:0)

好,首先,您可以将问题简化为 m 问题,因为切换到 modulo m时整数的属性不会改变字段。很容易证明被m整除与与0 mod m 相同。

我首先将所有这些数字转换为对应的{em> m ,并通过考虑a_i2*a_i3*a_i来消除重复。 ..直到rep_a_i * a_i,它们全都是 mod m 。最终,您获得了一个简化的集合,该集合最多包含m个元素。然后消除那里的所有零,因为它们对总和没有贡献。这很重要,原因有两个:

  • 它将您的问题从复杂度为O(a^n)的背包问题( NP-完全)转换为O(K)问题,因为其复杂度不取决于集合中元素的数量,但数量m
  • 您仍然可以计算大量数字。您可以将简化集视为背包问题,然后尝试检查(并进一步简化)轻松背包问题(其中不同值a_i遵循带有K > 2的几何序列的问题)< / li>

剩下的问题是一个Knapsack problem NP完全)或它的 P 变体之一。

如果您还不了解(无法将其简化为容易的背包问题),则必须减少a_i的数量,以使指数时间获得最小指数:)

编辑

(@ mss要求在评论中进行详细说明)假设您有m = 8,列表为1 2 4 6 12 14 22。简化 mod m 后,列表保留为:1 2 4 6 4 6 6,其中6被重复3次。我们必须考虑三个可能的重复6,因为它们可以有助于获得总和,但暂时不多,让我们考虑6*1 = 66*2 = 126*3 = 18,第一个是原始的6,第二个重复4(因此我们需要考虑列表中的3个4),第三个重复转换为{{1 }}。因此,现在,列表中有2。我们对1 2 4 6 4 4 2重复进行相同的操作(两个4遇到4,即8 mod 0 有助于求和,但我们必须保留一个m,因为这意味着您要通过重复编号来使目标0)进入m => 1 2 4 6 0 4 2 =(重新排序) => 1 2 4 6 0 0 2 => 0 1 2 2 4 6。这应该是要考虑的最终列表。因为它有0 1 2 4 6,所以您先验知道有一个这样的总和(在这种情况下,您将原始列表的{{1 }}和0数字。