线上报警问题分类 | 豆包MarsCode AI刷题

3 阅读6分钟

问题描述

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。

问题解析与思路

问题理解

这个问题可以分为两个主要部分:

  1. 用户实验记录:每个用户都有一个实验列表,表示该用户命中的实验。
  2. 查询条件:每次查询给出一个实验序列,表示命中或未命中的实验。我们需要统计符合这些条件的用户数量。
数据结构选择
  • 用户实验记录:使用二维数组 arrayN 存储每个用户的实验列表。
  • 查询条件:使用二维数组 arrayQ 存储每次查询的实验序列。
  • 命中和未命中的实验:使用 unordered_set 来存储命中的实验和未命中的实验,这样可以快速检查实验是否存在。
算法步骤
  1. 初始化结果数组:创建一个结果数组 result,用于存储每次查询的结果。
  2. 遍历每个查询:对于每个查询,获取查询的实验序列。
  3. 存储命中和未命中的实验:使用两个集合 hitExperiments 和 missExperiments 分别存储命中的实验和未命中的实验。
  4. 遍历每个用户:对于每个用户,检查其实验列表是否符合查询条件。
  5. 检查命中和未命中的实验:分别检查用户是否命中了所有需要的实验,并且未命中所有不需要的实验。
  6. 增加计数:如果用户符合查询条件,增加计数。

代码详解

#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;
}
  1. 初始化结果数组:我们首先初始化一个结果数组 result,用于存储每次查询的结果。
  2. 遍历每个查询:我们遍历每个查询,获取当前查询的实验序列。
  3. 使用集合存储实验:我们使用两个集合 hitExperiments 和 missExperiments 来分别存储命中的实验和未命中的实验。
  4. 遍历每个用户:我们遍历每个用户,检查当前用户是否符合查询条件。
  5. 检查命中和未命中的实验:我们分别检查用户是否命中了所有需要的实验,并且未命中所有不需要的实验。
  6. 增加计数:如果用户符合查询条件,我们增加计数。

知识总结与学习建议

知识总结
  1. 数据结构选择

    • 二维数组:用于存储用户实验记录和查询条件。
    • 集合:用于快速检查实验是否存在,提高查询效率。
  2. 算法设计

    • 遍历与检查:通过遍历用户和查询,检查用户是否符合查询条件。
    • 条件判断:使用条件判断来确定用户是否命中或未命中实验。
学习建议
  1. 理解数据结构:掌握常用的数据结构如数组、集合、哈希表等,了解它们的优缺点和适用场景。
  2. 算法设计:学习如何设计高效的算法,理解遍历、查找、排序等基本操作。
  3. 实践与调试:通过编写代码和调试,加深对数据结构和算法的理解。多做练习题,提高编程能力。
  4. 代码优化:在理解基本算法的基础上,尝试优化代码,提高运行效率。