今天我们来看CSP202206C.角色授权这道题目





题意分析
本题要求实现一个基于角色的访问控制(RBAC)模型。系统中有三类实体:
- 角色(Role):包含名称、允许的操作清单、允许的资源种类清单、允许的资源名称清单
- 角色关联(Role Association):将角色与用户或用户组关联
- 待授权行为(Authorization Request):包含用户信息、操作、资源信息
判断规则:
- 角色能否执行某操作:操作、资源种类、资源名称都要匹配(支持通配符
*,资源名称清单为空表示不限制) - 用户能否执行某操作:找到所有与该用户或所属用户组关联的角色,任一角色允许即可
解题思路
1. 数据结构设计
- 用结构体存储角色信息,使用
unordered_set存储操作、资源种类、资源名称,便于快速查找 - 用
unordered_map建立角色名称到索引的映射 - 用两个
unordered_map分别存储:- 用户名 → 关联的角色索引列表
- 用户组名 → 关联的角色索引列表
2. 核心算法
角色匹配判断:
- 检查操作是否在角色的操作清单中,或清单包含
* - 检查资源种类是否在角色的资源种类清单中,或清单包含
* - 检查资源名称是否在角色的资源名称清单中,或清单为空(不限制)
用户权限判断:
- 收集用户直接关联的所有角色
- 收集用户所属用户组关联的所有角色(需要去重)
- 遍历所有关联角色,任一角色允许该操作则返回允许
3. 关键点
- 独立判断:每个待授权行为独立判断,不存储历史信息
- 去重:一个用户可能通过多个途径关联到同一角色,需要去重避免重复判断
- 通配符处理 :
*表示匹配所有,空资源名称清单表示不限制 - 性能优化:使用哈希表存储和查找,保证 O(1) 的平均查找时间
4. 时间复杂度
- 每个角色的存储:O(nv + no + nn)
- 每次查询:O(关联角色数量),最坏 O(n)
- 总体:O(q × n),在数据范围内可行
代码实现
cpp
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <unordered_set>
using namespace std;
// 角色结构体
struct Role {
string name;
unordered_set<string> ops; // 操作清单
unordered_set<string> res_types; // 资源种类清单
unordered_set<string> res_names; // 资源名称清单(空表示不限制)
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); // 加速输入输出
int n, m, q;
cin >> n >> m >> q;
vector<Role> roles(n);
unordered_map<string, int> roleIdx; // 角色名到索引的映射
// 读取角色信息
for (int i = 0; i < n; ++i) {
Role &r = roles[i];
cin >> r.name;
roleIdx[r.name] = i;
// 读取操作清单
int nv;
cin >> nv;
for (int j = 0; j < nv; ++j) {
string op;
cin >> op;
r.ops.insert(op);
}
// 读取资源种类清单
int no;
cin >> no;
for (int j = 0; j < no; ++j) {
string rt;
cin >> rt;
r.res_types.insert(rt);
}
// 读取资源名称清单
int nn;
cin >> nn;
for (int j = 0; j < nn; ++j) {
string rn;
cin >> rn;
r.res_names.insert(rn);
}
// 如果 nn = 0,res_names 保持空,表示不限制
}
// 构建用户/组到角色的映射
unordered_map<string, vector<int>> userToRoles;
unordered_map<string, vector<int>> groupToRoles;
// 读取角色关联
for (int i = 0; i < m; ++i) {
string roleName;
cin >> roleName;
int idx = roleIdx[roleName]; // 获取角色索引
int ns;
cin >> ns;
for (int j = 0; j < ns; ++j) {
string type, name;
cin >> type >> name;
if (type == "u") {
userToRoles[name].push_back(idx);
} else { // type == "g"
groupToRoles[name].push_back(idx);
}
}
}
// 处理每个待授权的行为
for (int query = 0; query < q; ++query) {
string userName;
int ng;
cin >> userName >> ng;
// 读取用户所属用户组
vector<string> groups(ng);
for (int i = 0; i < ng; ++i) {
cin >> groups[i];
}
string op, resType, resName;
cin >> op >> resType >> resName;
// 标记匹配的角色(去重)
vector<char> matched(n, 0);
vector<int> matchedRoles;
matchedRoles.reserve(n);
// 处理用户直接关联的角色
auto itUser = userToRoles.find(userName);
if (itUser != userToRoles.end()) {
for (int idx : itUser->second) {
if (!matched[idx]) {
matched[idx] = 1;
matchedRoles.push_back(idx);
}
}
}
// 处理用户组关联的角色
for (const string &g : groups) {
auto itGroup = groupToRoles.find(g);
if (itGroup != groupToRoles.end()) {
for (int idx : itGroup->second) {
if (!matched[idx]) {
matched[idx] = 1;
matchedRoles.push_back(idx);
}
}
}
}
// 检查是否有任意匹配的角色允许该操作
bool allowed = false;
for (int idx : matchedRoles) {
const Role &r = roles[idx];
// 检查操作是否匹配
bool opOK = r.ops.count("*") || r.ops.count(op);
// 检查资源种类是否匹配
bool typeOK = r.res_types.count("*") || r.res_types.count(resType);
// 检查资源名称是否匹配(空清单表示不限制)
bool nameOK = r.res_names.empty() || r.res_names.count(resName);
// 三个条件都满足则允许
if (opOK && typeOK && nameOK) {
allowed = true;
break;
}
}
cout << (allowed ? 1 : 0) << '\n';
}
return 0;
}
总结
本题是典型的 RBAC(基于角色的访问控制)模型实现,主要考察:
- 数据结构设计 :合理使用哈希表(
unordered_set/unordered_map)存储和查找数据 - 逻辑判断:准确理解并实现三层判断逻辑(用户→角色→权限)
- 边界处理 :正确处理通配符
*、空清单、去重等特殊情况 - 性能优化:使用哈希表保证查询效率,避免超时
关键是要理清判断的层次关系:先确定用户关联的所有角色,再逐个检查这些角色的权限是否覆盖当前操作。使用哈希表能够高效地处理大量查询。