角色授权

建立授权对象到角色的反向索引,再取用户和当次用户组关联角色的权限并集。

OJ: shumeng

题目 ID: CSP202206C

难度:普及+/提高-

标签:哈希集合模拟

日期: 2026-07-31 16:21

形式化题目

nn 个角色,每个角色有操作清单、资源类型清单和资源名称清单三组字符串;资源名称清单为空表示可匹配任意名称,三张清单中的 * 均可匹配任意操作或任意类型。

mm 条角色关联,每条把某个角色授予若干用户或用户组。之后有 qq 次查询:给定用户、其所属用户组、一个操作、资源类型和资源名称,判断该用户能否执行此操作。

思路

用户权限等于其关联的所有角色权限的并集,因此关键是把“授权对象 → 角色”的反向关系建好,避免查询时扫描全部角色关联。

建立反向索引

把每个授权对象记录为带前缀的键:用户用 u:名字,用户组用 g:名字,值是它能获得的角色编号列表。读入 mm 条角色关联时直接填充这个 map

收集本次查询涉及的角色

每次查询读出用户和当次所属的用户组(题目强调不能记忆历史用户组,必须用本次给出的组),依次查询 u:用户g:组名 对应的角色编号。不同角色、不同组可能指向同一角色,用 seen 数组按查询编号去重。

判断权限

对收集到的每个角色检查三张清单:

  1. 操作清单为空或包含操作,或包含 *
  2. 类型清单包含类型或包含 *
  3. 名称清单为空或包含名称。

三条件都满足即可执行;任一角色满足就输出 1

代码

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-31 16:21
 * update_at: 2026-08-17 22:40
 */
#include <bits/stdc++.h>
using namespace std;

// 一个角色允许的操作、资源类型和资源名称三张清单
struct Role {
    string name;                                  // 角色名称
    unordered_set<string> operations;             // 允许的操作清单
    unordered_set<string> object_types;           // 允许的资源类型清单
    unordered_set<string> object_names;           // 允许的资源名称清单(空表示任意名称)
};

int role_count, relation_count, query_count;
vector<Role> role;
unordered_map<string, int> role_id;               // 角色名称 -> 编号
unordered_map<string, vector<int> > object_roles; // "u:用户" 或 "g:用户组" -> 可获得的角色编号列表
vector<int> seen;                                 // 去重标记:值为最近一次查询的编号
int query_id;

// 读入一个角色并填入角色表
void read_role(int id) {
    int count;
    string value;
    cin >> role[id].name;
    role_id[role[id].name] = id;

    cin >> count; // 操作清单
    for (int i = 0; i < count; i++) {
        cin >> value;
        role[id].operations.insert(value);
    }
    cin >> count; // 资源类型清单
    for (int i = 0; i < count; i++) {
        cin >> value;
        role[id].object_types.insert(value);
    }
    cin >> count; // 资源名称清单
    for (int i = 0; i < count; i++) {
        cin >> value;
        role[id].object_names.insert(value);
    }
}

// 读入一条角色关联:把该角色关联到若干用户或用户组
void read_relation() {
    string name;
    int count;
    cin >> name >> count;
    int id = role_id[name];
    for (int i = 0; i < count; i++) {
        string type, value;
        cin >> type >> value; // type 为 u 或 g
        object_roles[type + ":" + value].push_back(id);
    }
}

// 加入一个授权对象("u:xxx" 或 "g:xxx")对应的角色,用 seen 去重
void collect_roles(const string &key, vector<int> &candidate) {
    vector<int> &list = object_roles[key];
    for (int i = 0; i < (int)list.size(); i++) {
        int id = list[i];
        if (seen[id] != query_id) {
            seen[id] = query_id;
            candidate.push_back(id);
        }
    }
}

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

    cin >> role_count >> relation_count >> query_count;
    role.resize(role_count);
    for (int i = 0; i < role_count; i++) read_role(i);
    for (int i = 0; i < relation_count; i++) read_relation();

    seen.assign(role_count, 0);
    query_id = 0;
    while (query_count--) {
        string user, operation, object_type, object_name;
        int group_count;
        cin >> user >> group_count;
        vector<string> group(group_count);
        for (int i = 0; i < group_count; i++) cin >> group[i];
        cin >> operation >> object_type >> object_name;
        query_id++;

        vector<int> candidate; // 本次查询涉及的所有角色编号(已去重)
        collect_roles("u:" + user, candidate);
        for (int i = 0; i < group_count; i++) collect_roles("g:" + group[i], candidate);

        // 任一角色允许该操作即输出 1
        bool allowed = false;
        for (int i = 0; i < (int)candidate.size() && !allowed; i++) {
            Role &current = role[candidate[i]];
            bool operation_ok = current.operations.count(operation)
                || current.operations.count("*");
            bool type_ok = current.object_types.count(object_type)
                || current.object_types.count("*");
            bool name_ok = current.object_names.empty()
                || current.object_names.count(object_name);
            if (operation_ok && type_ok && name_ok) allowed = true;
        }
        cout << (allowed ? 1 : 0) << '\n';
    }

    return 0;
}

复杂度

设一次查询涉及的不同角色数为 kk,用户组数为 gg。收集角色和判断权限均为哈希集合操作,时间复杂度为 O(g+k)O(g+k);空间复杂度为 O(n+mns)O(n+m\cdot ns),其中 nsns 是关联清单长度。

总结

每次查询的用户组信息独立使用,不能缓存同名用户此前的组;角色权限判断是多角色权限的并集。用带前缀的反向索引把“用户/组 → 角色”的关系组织起来,是这类授权模拟题最直接有效的做法。