CSP-202303-3-LDAP

CSP-202303-3-LDAP

解题思路

一、数据结构的定义

1.unordered_map

unordered_map 是 C++ 标准库中的一种关联容器,它提供了基于键值对 的高效查找、插入和删除操作。在这里,使用 unordered_map 有几个优势:

  1. 快速查找unordered_map 的查找时间复杂度是 O(1),本题可以用于根据属性名和属性值查找对应的DN。
  2. 高效插入和删除unordered_map 的插入和删除操作同样是O(1)级别的。
  3. 键值对的唯一性unordered_map 中的键是唯一的,这符合属性名-属性值对应用户集合的关系,每个键对应一个用户集合。
  4. 无序性unordered_map 中的键值对是无序存储的,但对于属性名-属性值对应用户集合的映射关系而言,这通常是没有问题的,因为我们是通过属性名和属性值来进行查找,而不是关心它们的顺序

2.set

set 是 C++ 标准库中提供的一个有序、不重复的容器。它是基于**红黑树(Red-Black Tree)**实现的,这使得插入、删除和查找元素的操作在平均情况下都是 O(log n) 的时间复杂度。

  1. 有序性(Ordered)set 中的元素是按照升序排列的。插入元素时会根据元素的值进行排序。
  2. 不重复性(Unique)set 不允许相同的元素存在,每个元素在集合中是唯一的。
  3. 常见操作的时间复杂度
  • 插入(insert):O(log n)
  • 删除(erase):O(log n)
  • 查找(find):O(log n)
  • 遍历:O(n)

3.数据结构定义

  • unordered_map<int, unordered_map<int, set<int>>> attrName_attrVal_users;:存储属性名-属性值-用户DN的映射关系。
  • unordered_map<int, set<int>> attrName_users;:存储每个属性不为NA的用户DN集合。
  • set<int>:用于存储用户的唯一标识。

二、 字符串转整型函数

  • 思路比较简单,套路也很固定,直接记住就行

    cpp 复制代码
    int getNum(string exp) {
        int ans = 0;
        for (int i = 0; i < exp.size(); i++) {
            ans *= 10;
            ans += (exp[i] - '0');
        }
        return ans;
    }

三、 原子操作BASE_EXPR

  • AtomOP 的函数,用于处理原子操作BASE_EXPR。原子操作包括两种情况:属性值等于给定值(:)和属性值不等于给定值(~)。函数接受一个字符串 exp 作为参数,该字符串表示原子操作的表达式。

    cpp 复制代码
    	set<int> AtomOP(string exp) {
        int p = 0;
        set<int> ans;
        while (exp[p] != ':' && exp[p] != '~') p++; // 寻找分隔符 ':' 或 '~' 的位置
    
        // 提取属性名和属性值
        int a = getNum(exp.substr(0, p));
        int b = getNum(exp.substr(p + 1, exp.size()));
    
        if (exp[p] == ':') {
            // 属性值等于给定值的情况,将对应的用户集合插入到结果集中(没有对应元素插入为空)
            ans.insert(attrName_attrVal_users[a][b].begin(), attrName_attrVal_users[a][b].end());
        }
        else {
            // 属性值不等于给定值的情况
            ans.insert(attrName_users[a].begin(), attrName_users[a].end());
            set<int> dead; // dead 集合用于存储需要从结果集中删除的用户。
            // 遍历属性名-属性值映射,删除属性值等于给定值的用户
            for (auto it : attrName_attrVal_users[a][b]) { // 循环迭代所有具有属性名为 a 且属性值为 b 的用户DN(用户标识)
                if (ans.count(it)) {
                    dead.insert(it);
                }
            }
            for (auto it : dead) ans.erase(it);
        }
        return ans;
    }

四、表达式EXPR操作-递归

  • EXPR的处理采用的是递归的思路,即不断递归,直至找到原子操作并进行处理

1. 基本情况(原子操作)-- 直接调用

cpp 复制代码
if (exp[0] >= '0' && exp[0] <= '9') {
    ans = AtomOP(exp);
}

2. 复杂情况 -- 递归

  • 对括号的识别类似对算术表达式中括号的识别,由于括号中的内容只可能是EXPRBASE_EXPR,因此可以使用递归。

  • 接下来就是对左右括号的处理。具体思路是,循环内,每当遇到一个左括号 (,就将其推入栈中;每当遇到一个右括号 ),就从栈中弹出一个左括号。当栈为空时,意味着找到了与初始左括号相匹配的右括号,此时 p 的位置标记了第一个子表达式的结束位置。

    cpp 复制代码
    while (!br.empty()) {
        p++;
        if (exp[p] == '(') {
            br.push('(');
        }
        else if (exp[p] == ')') {
            br.pop();
        }
    }
  • 通过 ExprOP 函数递归处理第一个子表达式,子表达式的范围从 & 符号后的第一个字符开始,到 p - 2 的位置结束(因为 p 位置是第一个子表达式外的右括号,所以需要 -2 来排除 &( 这两个字符)

    cpp 复制代码
    ans1 = ExprOP(exp.substr(2, p - 2)); 

3. 逻辑与运算

  • 合并两个子表达式的结果集合 ans1 和 ans2 中的元素(取交集),实现了逻辑与操作(AND)的逻辑。
cpp 复制代码
for (auto it : ans1) {
	if (ans2.count(it)) {
		ans.insert(it);
	}
}

4. 逻辑或运算

  • 同样按照集合的思路,因为set本身就有去重的性质,因此直接两个子表达式的结果集合 ans1 和 ans2 中的元素相加即可(取并集 ),实现了逻辑或操作(OR)的逻辑。

    cpp 复制代码
    ans.insert(ans1.begin(), ans1.end());
    ans.insert(ans2.begin(), ans2.end());

完整代码

cpp 复制代码
#include <iostream>
#include <unordered_map>
#include <set>
#include <stack>
using namespace std;

int n;  // 用户数量
unordered_map<int, unordered_map<int, set<int>>> attrName_attrVal_users; // 存储 属性名-属性值-用户DN
unordered_map<int, set<int>> attrName_users;  // 存储该属性不为NA的用户DN set

// 字符串转整形
int getNum(string exp) {
    int ans = 0;
    for (int i = 0; i < exp.size(); i++) {
        ans *= 10;
        ans += (exp[i] - '0');
    }
    return ans;
}

// 原子操作
set<int> AtomOP(string exp) {
    int p = 0;
    set<int> ans;
    while (exp[p] != ':' && exp[p] != '~') p++; // 寻找分隔符 ':' 或 '~' 的位置

    // 提取属性名和属性值
    int a = getNum(exp.substr(0, p));
    int b = getNum(exp.substr(p + 1, exp.size()));

    if (exp[p] == ':') {
        // 属性值等于给定值的情况,将对应的用户集合插入到结果集中(没有对应元素插入为空)
        ans.insert(attrName_attrVal_users[a][b].begin(), attrName_attrVal_users[a][b].end());
    }
    else {
        // 属性值不等于给定值的情况
        ans.insert(attrName_users[a].begin(), attrName_users[a].end());
        set<int> dead; // dead 集合用于存储需要从结果集中删除的用户。
        // 遍历属性名-属性值映射,删除属性值等于给定值的用户
        for (auto it : attrName_attrVal_users[a][b]) { // 循环迭代所有具有属性名为 a 且属性值为 b 的用户DN(用户标识)
            if (ans.count(it)) {
                dead.insert(it);
            }
        }
        for (auto it : dead) ans.erase(it);
    }
    return ans;
}

// 表达式操作
set<int> ExprOP(string exp) {
    set<int> ans;

    // 基本情况(原子操作),直接调用
    if (exp[0] >= '0' && exp[0] <= '9') {
        ans = AtomOP(exp);
    }

    // 逻辑 AND 操作
    else if (exp[0] == '&') {
        int p = 1;
        set<int> ans1, ans2; // ans1 和 ans2 来存储两个子表达式的结果集
        stack<char> br;
        br.push('(');
        /*
        循环内,每当遇到一个左括号 (,就将其推入栈中;每当遇到一个右括号 ),
        就从栈中弹出一个左括号。当栈为空时,意味着找到了与初始左括号相匹配
        的右括号,此时 p 的位置标记了第一个子表达式的结束位置。
        */
        while (!br.empty()) {
            p++;
            if (exp[p] == '(') {
                br.push('(');
            }
            else if (exp[p] == ')') {
                br.pop();
            }
        }
        
        /*
        通过 ExprOP 函数递归处理第一个子表达式,子表达式的范围从 & 符号后的第
        一个字符开始,到 p - 2 的位置结束(因为 p 位置是第一个子表达式外的右
        括号,所以需要 -2 来排除 &( 这两个字符)
        */
        ans1 = ExprOP(exp.substr(2, p - 2)); // 对每个子表达式递归调用 ExprOP,直到不再出现"("则进入到基本操作

        int q = p + 2; // 跳过第一个子表达式的结束括号和一个额外的空格或字符
        br.push('(');
        while (!br.empty()) {
            q++;
            if (exp[q] == '(') {
                br.push('(');
            }
            else if (exp[q] == ')') {
                br.pop();
            }
        }
        ans2 = ExprOP(exp.substr(p + 2, q - p - 2));

        // 合并两个集合 ans1 和 ans2 中的元素,实现了逻辑与操作(AND)的逻辑
        for (auto it : ans1) {
            if (ans2.count(it)) { // 这一行检查元素 it 是否也存在于集合 ans2 中。count 方法在这里用于检查指定元素是否存在于集合中。如果存在,count 方法返回 1,否则返回 0
                ans.insert(it);
            }
        }
    }
    else if (exp[0] == '|') {
        int p = 1;
        set<int> ans1, ans2;
        stack<char> br;
        br.push('(');
        while (!br.empty()) {
            p++;
            if (exp[p] == '(') {
                br.push('(');
            }
            else if (exp[p] == ')') {
                br.pop();
            }
        }
        ans1 = ExprOP(exp.substr(2, p - 2));
        int q = p + 2;
        br.push('(');
        while (!br.empty()) {
            q++;
            if (exp[q] == '(') {
                br.push('(');
            }
            else if (exp[q] == ')') {
                br.pop();
            }
        }
        ans2 = ExprOP(exp.substr(p + 2, q - p - 2));
        // 实现逻辑与操作(AND)的逻辑。
        ans.insert(ans1.begin(), ans1.end());
        ans.insert(ans2.begin(), ans2.end());
    }
    return ans;
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        int dn;
        cin >> dn;
        int num;
        cin >> num;
        for (int j = 1; j <= num; j++) {
            int attrName;
            cin >> attrName;
            int attrVal;
            cin >> attrVal;
            attrName_attrVal_users[attrName][attrVal].insert(dn);
            attrName_users[attrName].insert(dn);
        }
    }

    // 输入操作表达式数量
    int m;
    cin >> m;

    // 处理每个操作表达式
    for (int i = 1; i <= m; i++) {
        string exp;
        cin >> exp;
        set<int> ans;
        ans = ExprOP(exp);

        // 输出结果
        for (auto it : ans) {
            cout << it << " ";
        }
        cout << endl;
    }

    return 0;
}
相关推荐
帅逼码农11 分钟前
有限域、伽罗瓦域、扩域、素域、代数扩张、分裂域概念解释
算法·有限域·伽罗瓦域
Jayen H16 分钟前
【优选算法】盛最多水的容器
算法
机跃22 分钟前
递归算法常见问题(Java)
java·开发语言·算法
lijiachang03071830 分钟前
设计模式(一):单例模式
c++·笔记·学习·程序人生·单例模式·设计模式·大学生
<但凡.33 分钟前
题海拾贝:蓝桥杯 2020 省AB 乘法表
c++·算法·蓝桥杯
pzx_0011 小时前
【LeetCode】94.二叉树的中序遍历
算法·leetcode·职场和发展
DogDaoDao1 小时前
leetcode 面试经典 150 题:矩阵置零
数据结构·c++·leetcode·面试·矩阵·二维数组·矩阵置零
我曾经是个程序员1 小时前
使用C#生成一张1G大小的空白图片
java·算法·c#
芒果de香蕉皮1 小时前
mavlink移植到单片机stm32f103c8t6,实现接收和发送数据
stm32·单片机·嵌入式硬件·算法·无人机
徐子童1 小时前
二分查找算法专题
数据结构·算法