问题描述
AB 实验作为迭代推荐策略的有力工具,经常会有新的实验开启以及一些无效策略的下线。而在这些迭代过程中,难免会遇到意料之外的情况,导致部分服务崩溃而不可用。为了避免系统的完全崩溃,工程师们会添加监控报警,第一时间通知值周同学处理。
在熟悉了系统一段时间之后,小茗同学也开始值周处理线上报警了。很快,小茗同学就收到了第一条报警日志。在报警的时间范围内,小茗同学收到了N名用户的反馈,每位用户编号为1到N。小茗同学查询线上实验后,统计了用户命中实验的列表,第i位用户命中了ki个实验,第j个实验的编号为 ai,j。
这些用户的反馈不完全是由于一个问题造成的,因此小茗同学需要对这些问题进行分类。小茗同学根据先前的经验会进行 Q 次询问尝试问题分类,第ii次询问给出一个序列bi,1,bi,2,…,bi,cibi,1,bi,2,…,bi,ci,ci表示第i次查询的实验数量。当bi,j>0bi,j>0时表示命中实验∣bi,j∣∣bi,j∣,否则表示未命中实验∣bi,j∣∣bi,j∣,小茗同学需要得到符合这些条件的用户数。例如,序列 1,-2,3
表示命中实验 1,3
而没有命中实验 2
的用户数量。
小茗同学初来乍到,想要请你帮忙解决这个问题。
输入格式
第一行输入三个整数NMQ,N表示用户数,M表示实验数,Q表示询问次数。
接下来NN行输入用户命中的实验序列。 每行的第一个数为ki,表示第i(1≤i≤N)个用户命中的实验数量,然后是ki个数ai,j,表示该用户命中的第j(1≤j≤ki)个实验的编号。
接下来QQ行为小茗同学想要查询的实验序列。 每行的第一个数为ci,表示第i(1≤Q)个查询的实验数量,然后是ci个数bi,j,∣bi,j∣表示该查询的第j(1≤ci)个实验编号。
输出格式
输出Q行,每行一个整数,表示符合查询条件的用户数。
输入样例
3 3 3
2 1 2
2 2 3
2 1 3
2 1 -2
2 2 -3
2 3 -1
输出样例
1
1
1
数据范围
数据保证 1≤ai,j≤M,1≤∣bi,j∣≤M1≤ai,j≤M,1≤∣bi,j∣≤M。
50% 数据满足: 1≤N≤100,1≤M≤10,1≤Q,∑ci≤3001≤N≤100,1≤M≤10,1≤Q,∑ci≤300。
100% 数据满足: 1≤N≤105,1≤M≤100,1≤Q,∑ci≤100001≤N≤105,1≤M≤100,1≤Q,∑ci≤10000。
问题解析与思路
问题理解
这个问题可以分为两个主要部分:
- 用户实验记录:每个用户都有一个实验列表,表示该用户命中的实验。
- 查询条件:每次查询给出一个实验序列,表示命中或未命中的实验。我们需要统计符合这些条件的用户数量。
数据结构选择
- 用户实验记录:使用二维数组
arrayN
存储每个用户的实验列表。 - 查询条件:使用二维数组
arrayQ
存储每次查询的实验序列。 - 命中和未命中的实验:使用
unordered_set
来存储命中的实验和未命中的实验,这样可以快速检查实验是否存在。
算法步骤
- 初始化结果数组:创建一个结果数组
result
,用于存储每次查询的结果。 - 遍历每个查询:对于每个查询,获取查询的实验序列。
- 存储命中和未命中的实验:使用两个集合
hitExperiments
和missExperiments
分别存储命中的实验和未命中的实验。 - 遍历每个用户:对于每个用户,检查其实验列表是否符合查询条件。
- 检查命中和未命中的实验:分别检查用户是否命中了所有需要的实验,并且未命中所有不需要的实验。
- 增加计数:如果用户符合查询条件,增加计数。
代码详解
#include <vector>
#include <unordered_set>
using namespace std;
vector<int> solution(int n, int m, int q, const vector<vector<int>>& arrayN, const vector<vector<int>>& arrayQ) {
// 初始化结果数组,用于存储每次查询的结果
vector<int> result(q, 0);
// 遍历每个查询
for (int i = 0; i < q; ++i) {
// 获取当前查询的实验序列
const vector<int>& query = arrayQ[i];
int c_i = query[0];
// 使用两个集合来存储命中的实验和未命中的实验
unordered_set<int> hitExperiments;
unordered_set<int> missExperiments;
// 遍历查询中的每个实验
for (int j = 1; j <= c_i; ++j) {
int experiment = query[j];
if (experiment > 0) {
// 如果实验编号为正,表示命中该实验
hitExperiments.insert(experiment);
} else {
// 如果实验编号为负,表示未命中该实验
missExperiments.insert(-experiment);
}
}
// 遍历每个用户
for (int user = 0; user < n; ++user) {
const vector<int>& userExperiments = arrayN[user];
int k_i = userExperiments[0];
// 检查当前用户是否符合查询条件
bool matchesQuery = true;
for (int experiment : hitExperiments) {
// 检查用户是否命中了所有需要的实验
if (find(userExperiments.begin() + 1, userExperiments.end(), experiment) == userExperiments.end()) {
matchesQuery = false;
break;
}
}
if (!matchesQuery) continue;
for (int experiment : missExperiments) {
// 检查用户是否未命中所有不需要的实验
if (find(userExperiments.begin() + 1, userExperiments.end(), experiment) != userExperiments.end()) {
matchesQuery = false;
break;
}
}
if (!matchesQuery) continue;
// 如果用户符合查询条件,增加计数
result[i]++;
}
}
return result;
}
int main() {
// Add your test cases here
vector<vector<int>> arrayN = {{2, 1, 2}, {2, 2, 3}, {2, 1, 3}};
vector<vector<int>> arrayQ = {{2, 1, -2}, {2, 2, -3}, {2, 3, -1}};
vector<int> result = solution(3, 3, 3, arrayN, arrayQ);
vector<int> expected = {1, 1, 1};
if (result == expected) {
cout << "Test passed!" << endl;
} else {
cout << "Test failed!" << endl;
}
return 0;
}
- 初始化结果数组:我们首先初始化一个结果数组
result
,用于存储每次查询的结果。 - 遍历每个查询:我们遍历每个查询,获取当前查询的实验序列。
- 使用集合存储实验:我们使用两个集合
hitExperiments
和missExperiments
来分别存储命中的实验和未命中的实验。 - 遍历每个用户:我们遍历每个用户,检查当前用户是否符合查询条件。
- 检查命中和未命中的实验:我们分别检查用户是否命中了所有需要的实验,并且未命中所有不需要的实验。
- 增加计数:如果用户符合查询条件,我们增加计数。
知识总结与学习建议
知识总结
-
数据结构选择:
- 二维数组:用于存储用户实验记录和查询条件。
- 集合:用于快速检查实验是否存在,提高查询效率。
-
算法设计:
- 遍历与检查:通过遍历用户和查询,检查用户是否符合查询条件。
- 条件判断:使用条件判断来确定用户是否命中或未命中实验。
学习建议
- 理解数据结构:掌握常用的数据结构如数组、集合、哈希表等,了解它们的优缺点和适用场景。
- 算法设计:学习如何设计高效的算法,理解遍历、查找、排序等基本操作。
- 实践与调试:通过编写代码和调试,加深对数据结构和算法的理解。多做练习题,提高编程能力。
- 代码优化:在理解基本算法的基础上,尝试优化代码,提高运行效率。