CSP202303C.LDAP

今天,我们来看CSP202303C.LDAP这道题目

题意分析

本题要求实现一个 LDAP 用户匹配算法。给定若干用户,每个用户有唯一的 DN(正整数)和若干属性(属性编号和属性值均为正整数)。再给出多个匹配表达式,每个表达式是由原子表达式通过逻辑操作符(&|)组合而成的树形结构。原子表达式形式为 <属性编号><操作符><属性值>,其中操作符 : 表示断言(属性值相等),~ 表示反断言(属性值不等)。逻辑操作符 & 表示两个子表达式均匹配,| 表示至少一个子表达式匹配。

要求对于每个表达式,输出所有匹配用户的 DN,按升序排列。

数据范围 :用户数 n ≤ 2500,表达式数 m ≤ 500,每个用户属性个数不超过 500,属性编号和值均不超过 10^9,表达式长度不超过 2000。

思路

1. 数据结构设计

由于属性编号可能很大且稀疏,不能直接使用数组下标。我们使用 unordered_map 将原始属性编号映射为内部连续的 ID。对于每个属性 ID,维护:

  • value_to_users:一个 unordered_map<int, vector<int>>,记录该属性下特定属性值对应的所有用户 DN 列表。
  • all_users:一个 vector<int>,记录具有该属性的所有用户 DN 列表(不论属性值)。

这些列表在构建后都需要排序,以便后续高效地进行集合运算。

2. 表达式解析与求值

表达式符合 BNF 语法,可以使用递归下降解析器。解析过程中构建表达式树,但为了避免指针和内存管理,我们使用节点池 vector<Node>,每个节点存储子节点的索引。

节点结构:

cpp 复制代码
struct Node {
    bool is_atom;
    // 原子表达式信息
    int attr;
    char op;
    int val;
    // 逻辑表达式信息
    char logic;
    int left, right;
};

解析函数 parse_expr 返回节点在 nodes 中的索引。遇到逻辑操作符 &| 时,递归解析左右子表达式;遇到数字则解析为原子表达式。

求值函数 evaluate 递归处理节点:

  • 原子节点:根据操作符 :~ 获取匹配用户列表。
  • 逻辑节点:对左右子结果进行交集(&)或并集(|)操作。

由于用户列表已排序,我们可以实现有序集合的交、并、差运算,时间复杂度为 O(len1 + len2)。

3. 优化:结果缓存

同一个表达式中可能出现相同的原子表达式(例如 &(1:2)(1:2)),或者多次查询中重复出现。为了避免重复计算,使用 unordered_map 对原子表达式结果进行缓存。缓存键由属性内部 ID、操作符和属性值组成(自定义结构体 AtomKey 及其哈希)。

4. 关键操作实现

  • 交集:两个有序列表的公共元素。
  • 并集:两个有序列表的合并去重。
  • 差集 :在有序列表 a 中去除有序列表 b 中出现的元素,用于反断言操作。

5. 复杂度分析

设每个用户最多有 k 个属性,总属性记录数为 N = n * k

  • 构建属性映射和排序:每个属性列表长度不超过该属性出现次数,总体排序复杂度 O(N log N)。
  • 每个表达式求值:
    • 解析 O(L),L ≤ 2000。
    • 原子操作通过缓存和有序列表直接获取,复杂度 O(len) 或 O(1)(若列表已存在)。
    • 逻辑操作合并有序列表,总体复杂度与结果大小相关。最坏情况下所有用户都匹配,合并操作 O(n)。
  • 整体复杂度足以通过。

代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

// 属性数据结构
struct AttrData {
    unordered_map<int, vector<int>> value_to_users; // 属性值 -> 用户DN列表
    vector<int> all_users;                          // 所有具有该属性的用户DN
};

// 全局变量
unordered_map<int, int> attr_id_map;                // 原始属性编号 -> 内部ID
vector<AttrData> attributes;                        // 按内部ID存储的属性数据

// 原子表达式缓存键
struct AtomKey {
    int attr_id;
    char op;
    int val;
    bool operator==(const AtomKey& other) const {
        return attr_id == other.attr_id && op == other.op && val == other.val;
    }
};

struct AtomKeyHash {
    size_t operator()(const AtomKey& k) const {
        size_t h1 = hash<int>()(k.attr_id);
        size_t h2 = hash<char>()(k.op);
        size_t h3 = hash<int>()(k.val);
        return h1 ^ (h2 << 1) ^ (h3 << 2);
    }
};

unordered_map<AtomKey, vector<int>, AtomKeyHash> atom_cache;

// 表达式树节点(无指针,用索引表示子节点)
struct Node {
    bool is_atom;
    // 原子
    int attr;       // 原始属性编号
    char op;        // ':' 或 '~'
    int val;        // 属性值
    // 逻辑
    char logic;     // '&' 或 '|'
    int left, right; // 子节点索引,-1表示无

    Node() : is_atom(false), attr(0), op(0), val(0), logic(0), left(-1), right(-1) {}

    static Node make_atom(int a, char o, int v) {
        Node node;
        node.is_atom = true;
        node.attr = a;
        node.op = o;
        node.val = v;
        return node;
    }

    static Node make_logic(char l, int lc, int rc) {
        Node node;
        node.is_atom = false;
        node.logic = l;
        node.left = lc;
        node.right = rc;
        return node;
    }
};

// 解析表达式,nodes为节点池,pos为当前解析位置,返回节点索引
int parse_expr(const string& s, int& pos, vector<Node>& nodes) {
    if (pos >= s.size()) return -1;
    char c = s[pos];
    if (c == '&' || c == '|') {
        char logic = c;
        pos++;          // 跳过逻辑符
        pos++;          // 跳过 '('
        int left_idx = parse_expr(s, pos, nodes);
        pos++;          // 跳过 ')'
        pos++;          // 跳过 '('
        int right_idx = parse_expr(s, pos, nodes);
        pos++;          // 跳过 ')'
        int idx = nodes.size();
        nodes.push_back(Node::make_logic(logic, left_idx, right_idx));
        return idx;
    } else {
        // 解析属性编号
        int start = pos;
        while (pos < s.size() && isdigit(s[pos])) pos++;
        int attr = stoi(s.substr(start, pos - start));
        char op = s[pos]; // ':' 或 '~'
        pos++;
        // 解析属性值
        start = pos;
        while (pos < s.size() && isdigit(s[pos])) pos++;
        int val = stoi(s.substr(start, pos - start));
        int idx = nodes.size();
        nodes.push_back(Node::make_atom(attr, op, val));
        return idx;
    }
}

// 差集:a - b(a, b 有序)
vector<int> difference(const vector<int>& a, const vector<int>& b) {
    vector<int> res;
    size_t i = 0, j = 0;
    while (i < a.size()) {
        if (j >= b.size() || a[i] < b[j]) {
            res.push_back(a[i++]);
        } else if (a[i] > b[j]) {
            j++;
        } else { // 相等
            i++;
            j++;
        }
    }
    return res;
}

// 交集(a, b 有序)
vector<int> intersection(const vector<int>& a, const vector<int>& b) {
    vector<int> res;
    size_t i = 0, j = 0;
    while (i < a.size() && j < b.size()) {
        if (a[i] < b[j]) i++;
        else if (a[i] > b[j]) j++;
        else {
            res.push_back(a[i]);
            i++;
            j++;
        }
    }
    return res;
}

// 并集(a, b 有序,无重复)
vector<int> union_set(const vector<int>& a, const vector<int>& b) {
    vector<int> res;
    size_t i = 0, j = 0;
    while (i < a.size() || j < b.size()) {
        if (j >= b.size() || (i < a.size() && a[i] < b[j])) {
            res.push_back(a[i++]);
        } else if (i >= a.size() || b[j] < a[i]) {
            res.push_back(b[j++]);
        } else { // 相等
            res.push_back(a[i]);
            i++;
            j++;
        }
    }
    return res;
}

// 评估表达式树,返回有序用户列表
vector<int> evaluate(int node_idx, const vector<Node>& nodes) {
    const Node& node = nodes[node_idx];
    if (node.is_atom) {
        // 查找属性内部ID
        auto it = attr_id_map.find(node.attr);
        if (it == attr_id_map.end()) {
            return {}; // 没有任何用户具有该属性
        }
        int attr_id = it->second;

        // 构造缓存键
        AtomKey key{attr_id, node.op, node.val};
        auto cache_it = atom_cache.find(key);
        if (cache_it != atom_cache.end()) {
            return cache_it->second; // 命中缓存
        }

        vector<int> result;
        const auto& ad = attributes[attr_id];
        if (node.op == ':') {
            // 断言:值相等
            auto vit = ad.value_to_users.find(node.val);
            if (vit != ad.value_to_users.end()) {
                result = vit->second; // 拷贝
            }
        } else { // '~'
            // 反断言:值不等
            auto vit = ad.value_to_users.find(node.val);
            if (vit == ad.value_to_users.end()) {
                result = ad.all_users; // 所有用户都匹配
            } else {
                result = difference(ad.all_users, vit->second);
            }
        }

        atom_cache[key] = result;
        return result;
    } else {
        vector<int> left_res = evaluate(node.left, nodes);
        vector<int> right_res = evaluate(node.right, nodes);
        if (node.logic == '&') {
            return intersection(left_res, right_res);
        } else { // '|'
            return union_set(left_res, right_res);
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int n;
    cin >> n;

    // 读入用户数据
    for (int i = 0; i < n; ++i) {
        int dn, k;
        cin >> dn >> k;
        for (int j = 0; j < k; ++j) {
            int attr, val;
            cin >> attr >> val;
            int attr_id;
            auto it = attr_id_map.find(attr);
            if (it == attr_id_map.end()) {
                attr_id = attributes.size();
                attr_id_map[attr] = attr_id;
                attributes.emplace_back();
            } else {
                attr_id = it->second;
            }
            attributes[attr_id].value_to_users[val].push_back(dn);
            attributes[attr_id].all_users.push_back(dn);
        }
    }

    // 对所有列表排序
    for (auto& ad : attributes) {
        sort(ad.all_users.begin(), ad.all_users.end());
        for (auto& pair : ad.value_to_users) {
            sort(pair.second.begin(), pair.second.end());
        }
    }

    int m;
    cin >> m;
    cin.ignore(); // 忽略换行符

    for (int i = 0; i < m; ++i) {
        string expr;
        getline(cin, expr);
        if (expr.empty()) {
            cout << '\n';
            continue;
        }

        vector<Node> nodes; // 本次表达式的节点池
        int pos = 0;
        int root_idx = parse_expr(expr, pos, nodes);
        vector<int> res = evaluate(root_idx, nodes);

        // 输出结果
        for (size_t j = 0; j < res.size(); ++j) {
            if (j > 0) cout << ' ';
            cout << res[j];
        }
        cout << '\n';
    }

    return 0;
}

总结

本题是一道典型的 表达式解析 + 集合运算 问题。核心要点包括:

  1. 属性映射与索引:由于属性编号范围大且稀疏,使用哈希表映射到连续内部 ID 以便高效存储。
  2. 有序列表维护:预排序用户 DN 列表,使得后续交集、并集、差集操作只需线性时间完成。
  3. 递归解析表达式:基于 BNF 语法构建表达式树,使用节点池而非指针简化内存管理。
  4. 结果缓存:对原子表达式结果进行缓存,避免重复计算,提升效率。

通过以上设计,算法能够在大数据范围内高效运行,并保持代码清晰易读。

相关推荐
奋斗吧程序媛1 小时前
CSV文件导入功能
开发语言·javascript·ecmascript
evans在进步1 小时前
Java 常用设计模式(二):装饰器、适配器、责任链、模板方法、策略与观察者
java·开发语言·设计模式
小雨笙笙1 小时前
C语言:指针
c语言·开发语言
j7~2 小时前
【C++】《unordered系列关联式容器以及哈希底层、哈希冲突、闭散列与开散列完整解析》
c++·哈希算法·开散列·闭散列·unordered_map·unordered_set·哈希底层结构
旧梦95272 小时前
Java EnumMap 详解:原理、用法与实战
java·开发语言
2402_882893862 小时前
封装红黑树实现map和set —— 从源码到模拟实现
c++·set·map
snow@li3 小时前
Java:微服务项目与单体项目区别、市场占有率全景深度分析
java·开发语言·微服务
吴声子夜歌3 小时前
Java——性能和效率
java·开发语言
小鹿的周先生3 小时前
第四章-SpringAI-函数调用&ToolCalling
开发语言·人工智能·python