我正在研究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;
}
答案 0 :(得分:1)
不需要value
。找到有效组合后,即如果find_it
返回true
,则可以在所有递归调用中立即返回true。
一些补充说明:
int a[n]
中的可变大小数组不是标准C ++,因此不适用于所有编译器。m
而不是int&
作为int
传递。map
与set
相同,其中假定元素在集合中映射到true
,如果在集合中则映射到false
不。考虑使用unordered_set
代替unordered_map
。unordered_map
很昂贵。您可以轻松地将两个密钥都放入std::pair
中并将其用作密钥。这样可以避免维护地图的开销。bits/stdc++.h
也是非标准的,您应该指定正确的头文件,例如#include <unordered_map>
和#include <iostream>
。>
允许它正确解析,也应该在变量类型和它的名称之间放置空格。它使代码难以阅读。答案 1 :(得分:0)
好,首先,您可以将问题简化为 m
问题,因为切换到 modulo m
时整数的属性不会改变字段。很容易证明被m
整除与与0
mod m
相同。
我首先将所有这些数字转换为对应的{em> m
,并通过考虑a_i
,2*a_i
,3*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 = 6
,6*2 = 12
和6*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
数字。