题解:AtCoder AT_awc0048_e Team Formation
【题目来源】
AtCoder:E - Team Formation
【题目描述】
Takahashi is organizing a quiz competition for his school's cultural festival. There are \(N\) students participating in the quiz competition, and student \(i\) (\(1 \leq i \leq N\)) has a skill value \(A_i\). Even if some students have the same skill value, each student is distinguished as a different person.
高桥正在为他学校的文化节组织一场知识竞赛。有 \(N\) 名学生参加比赛,学生 \(i\)(\(1 \leq i \leq N\))具有技能值 \(A_i\)。即使有些学生的技能值相同,每位学生也作为不同的人来区分。
Takahashi will select exactly \(K\) people from these \(N\) students to form one team. Each student can only be selected once. To make the competition fair, he wants the sum of the skill values of the selected \(K\) people to be a multiple of \(M\).
高桥将从这 \(N\) 名学生中恰好选择 \(K\) 人来组成一支队伍。每名学生只能被选中一次。为了使比赛公平,他希望被选中的 \(K\) 人的技能值之和是 \(M\) 的倍数。
Find the number of ways to select the students that satisfy the condition, modulo \(10^9 + 7\).
求满足条件的选择学生的方案数,结果对 \(10^9 + 7\) 取模。
Here, a positive integer \(X\) is a multiple of \(M\) if there exists a positive integer \(k\) such that \(X = kM\).
这里,正整数 \(X\) 是 \(M\) 的倍数,当存在一个正整数 \(k\) 使得 \(X = kM\)。
【输入】
\(N\) \(K\) \(M\)
\(A_1\) \(A_2\) \(\ldots\) \(A_N\)
- The first line contains the integer \(N\) representing the number of students, the integer \(K\) representing the number of people to select, and the integer \(M\) which is the base for the multiple condition, separated by spaces.
- The second line contains the integers \(A_1, A_2, \ldots, A_N\) representing the skill values of each student, separated by spaces.
【输出】
Print on one line the number of ways to select exactly \(K\) people from \(N\) students such that the sum of their skill values is a multiple of \(M\), modulo \(10^9 + 7\).
【输入样例】
4 2 3
1 2 3 4
【输出样例】
2
【核心思想】
-
问题分析:给定 \(N\) 个学生的技能值 \(A_i\),需要恰好选择 \(K\) 人,使得技能值之和是 \(M\) 的倍数。\(N \leq 34\),直接枚举 \(C(N, K)\) 会超时。这是一个折半搜索 + 哈希表计数问题,关键在于将集合分成两半,分别枚举子集后用哈希表快速匹配。
-
算法选择:
- 折半搜索:将 \(N\) 个数分成两半(前 \(N/2\) 个和后 \(N/2\) 个),分别枚举子集,将复杂度从 \(O(2^N)\) 降为 \(O(2^{N/2})\)
- 哈希表计数:用
unordered_map<int, int> mp1[cnt][sum]记录前半部分选择cnt个元素、和模 \(M\) 为sum的子集个数 - 模运算处理:对于后半部分的每个子集,计算需要从前半部分找到的余数
need_sum = (M - sum) % M
-
关键步骤:
- 分治:将数组分成前后两半,大小分别为
maxn1 = n/2和maxn2 = n - maxn1 - 枚举前半部分:用位运算枚举所有子集,记录
(选择个数,和模M)的组合出现次数到mp1 - 枚举后半部分:用位运算枚举所有子集,对于每个子集:
- 计算
need_cnt = k - cnt:需要从前半部分选择的元素个数 - 计算
need_sum = (M - sum) % M:需要从前半部分找到的和模 \(M\) 值 - 查询
mp1[need_cnt][need_sum]并累加到答案
- 计算
- 输出答案:结果对 \(10^9 + 7\) 取模
- 分治:将数组分成前后两半,大小分别为
-
时间/空间复杂度:
- 时间复杂度:\(O(2^{N/2} \times N)\),枚举每半部分的子集需要 \(O(2^{N/2})\),每个子集计算和需要 \(O(N)\)
- 空间复杂度:\(O(2^{N/2})\),存储前半部分子集信息
-
折半搜索 + 哈希表的核心思想:
- 分治降低复杂度:将指数级复杂度从 \(O(2^N)\) 降为 \(O(2^{N/2})\)
- 哈希表快速匹配:用哈希表存储前半部分信息,支持 \(O(1)\) 查询匹配
- 多维状态记录:用
mp1[cnt][sum]同时记录选择个数和余数两个维度 - 模运算技巧:利用
(M - sum) % M找到互补余数,使得两部分之和模 \(M\) 为 0 - 适用于需要统计满足特定条件的子集个数的计数问题
【算法标签】
折半搜索
【代码详解】
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 35, mod = 1e9 + 7;
int n, k, m; // n: 数组长度, k: 需要选择的元素个数, m: 模数
int a[N]; // 存储数组元素
unordered_map<int, int> mp1[N]; // 哈希表数组,mp1[i]存储选择i个元素时的余数频率
signed main()
{
// 读取输入
int n, k; // 这里重新定义了n,k,覆盖了全局变量
cin >> n >> k >> m;
// 读取数组
for (int i = 1; i <= n; i++)
{
cin >> a[i];
}
// 将数组分成两半
int maxn1 = n / 2; // 前半部分大小
int maxn2 = n - maxn1; // 后半部分大小
// 第一部分:枚举前半部分所有子集
for (int mask = 0; mask < (1 << maxn1); mask++)
{
int cnt = 0; // 当前子集选择的元素个数
int sum = 0; // 当前子集和对m取模的结果
for (int i = 1; i <= maxn1; i++)
{
if (mask >> (i - 1) & 1) // 检查第i位是否为1
{
cnt++;
sum = (sum + a[i]) % m; // 累加并取模
}
}
// 只记录选择元素个数不超过k的子集
if (cnt <= k)
{
mp1[cnt][sum]++; // 记录该余数出现的次数
}
}
int ans = 0; // 最终结果
// 第二部分:枚举后半部分所有子集
for (int mask = 0; mask < (1 << maxn2); mask++)
{
int cnt = 0; // 当前子集选择的元素个数
int sum = 0; // 当前子集和对m取模的结果
for (int i = 1; i <= maxn2; i++)
{
if (mask >> (i - 1) & 1) // 检查第i位是否为1
{
cnt++;
sum = (sum + a[i + maxn1]) % m; // 注意索引偏移
}
}
// 只处理选择元素个数不超过k的子集
if (cnt <= k)
{
int need_cnt = k - cnt; // 需要从前半部分选择的元素个数
if (need_cnt >= 0 && need_cnt <= k)
{
int need_sum = (m - sum) % m; // 需要从前半部分找到的余数
if (mp1[need_cnt].count(need_sum)) // 如果存在匹配的余数
{
// 累加组合数
ans = (ans + mp1[need_cnt][need_sum]) % mod;
}
}
}
}
cout << ans << endl;
return 0;
}
【运行结果】
4 2 3
1 2 3 4
2
浙公网安备 33010602011771号