题目链接
题目描述
给定一个含有 (
) 个正整数的数组
以及一个正整数模数
(
)。你可以任选若干下标递增的元素构成一个子序列(允许选择空序列)。设所选元素之和为
,求
的最大可能值。
解题思路
这是一个求解所有子序列和模 的最大值问题。
1. 算法选择
本题的关键在于数据范围:数组长度 非常小(
),而模数
和数组元素
可能非常大。
的动态规划解法:由于
可以达到
,时间和空间复杂度都无法接受。
的搜索解法:由于
,
,这是一个非常小的计算量。因此,我们可以通过枚举所有可能的子序列来解决问题。
深度优先搜索(DFS)是实现子序列枚举的经典方法。
2. 深度优先搜索 (DFS) 流程
我们可以定义一个递归函数,例如 dfs(index, current_sum_mod_m)
,来探索所有可能性。
-
函数参数:
index
:当前正考虑数组a
中的第index
个元素。current_sum_mod_m
:到目前为止,已选子序列的元素和对取模后的值。
-
递归逻辑: 在
dfs(index, current_sum_mod_m)
中,对于元素a[index]
,我们有两个选择:- 不选择
a[index]
: 子序列和不变,我们继续处理下一个元素。递归调用dfs(index + 1, current_sum_mod_m)
。 - 选择
a[index]
: 新的子序列和为(current_sum_mod_m + a[index]) % m
。我们继续处理下一个元素。递归调用dfs(index + 1, (current_sum_mod_m + a[index]) % m)
。
- 不选择
-
递归终止条件: 当
index == n
时,说明我们已经对所有元素做出了选择,形成了一个完整的子序列。此时的current_sum_mod_m
就是这个子序列的和模的值。我们将这个值记录下来。
-
收集与去重: 我们可以使用一个
set
(或HashSet
) 来存储所有在递归终止时得到的current_sum_mod_m
。set
可以自动处理重复的和值。 -
最终答案: DFS结束后,
set
中存储了所有可能的子序列和模的值。我们需要找到这个
set
中的最大元素。不要忘记,空子序列(和为0)也是一个合法的选项,所以需要将0
也考虑在内。
代码
#include <iostream>
#include <vector>
#include <set>
#include <algorithm>
using namespace std;
int n;
long long m;
vector<long long> a;
set<long long> possible_sums;
void dfs(int index, long long current_sum) {
if (index == n) {
possible_sums.insert(current_sum);
return;
}
// 不选择 a[index]
dfs(index + 1, current_sum);
// 选择 a[index]
dfs(index + 1, (current_sum + a[index]) % m);
}
int main() {
cin >> n >> m;
a.resize(n);
for (int i = 0; i < n; ++i) {
cin >> a[i];
}
// 初始调用,代表从第0个元素开始,当前和为0
dfs(0, 0);
// set 中最后一个元素即为最大值
if (possible_sums.empty()) {
cout << 0 << endl;
} else {
cout << *possible_sums.rbegin() << endl;
}
return 0;
}
import java.util.Scanner;
import java.util.HashSet;
import java.util.Collections;
public class Main {
private static int n;
private static long m;
private static long[] a;
private static HashSet<Long> possibleSums;
private static void dfs(int index, long currentSum) {
if (index == n) {
possibleSums.add(currentSum);
return;
}
// 不选择 a[index]
dfs(index + 1, currentSum);
// 选择 a[index]
dfs(index + 1, (currentSum + a[index]) % m);
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextLong();
a = new long[n];
for (int i = 0; i < n; i++) {
a[i] = sc.nextLong();
}
possibleSums = new HashSet<>();
dfs(0, 0);
if (possibleSums.isEmpty()) {
System.out.println(0);
} else {
System.out.println(Collections.max(possibleSums));
}
}
}
import sys
# 对于深度较深的递归,可能需要增加递归深度限制
# 在本题 n <= 15 的情况下,通常不是必需的
# sys.setrecursionlimit(20)
n, m = 0, 0
a = []
possible_sums = set()
def dfs(index, current_sum):
if index == n:
possible_sums.add(current_sum)
return
# 不选择 a[index]
dfs(index + 1, current_sum)
# 选择 a[index]
dfs(index + 1, (current_sum + a[index]) % m)
def solve():
global n, m, a
try:
line = input()
if not line: return
n, m = map(int, line.split())
line = input()
if not line: return
a = list(map(int, line.split()))
except (IOError, ValueError):
return
possible_sums.clear()
dfs(0, 0)
if not possible_sums:
print(0)
else:
print(max(possible_sums))
solve()
算法及复杂度
-
算法:深度优先搜索(DFS)、回溯法
-
时间复杂度:
。对于数组中的
个元素,每个元素都有“选”和“不选”两种状态,总共构成
种不同的子序列。DFS树有
个节点,所以复杂度是
。
-
空间复杂度:
。在最坏情况下,可能会产生
个不同的子序列和,我们需要使用
set
来存储它们。同时,递归调用栈的深度为,但存储结果的空间占主导。