【CCF-CSP】 202303-3 LDAP

题目背景

西西艾弗岛运营公司是一家负责维护和运营岛上基础设施的大型企业,拥有数千名员工。公司内有很多 IT 系统。 为了能够实现这些 IT 系统的统一认证登录,公司 IT 部门决定引入一套 LDAP 系统来管理公司内的用户信息。 轻型目录访问协议(Lightweight Directory Access Protocol,LDAP)是一种用于访问和维护目录服务的应用层协议, 基于它的数据库可以用树形结构来组织和存储数据。每一笔数据,都包含了一个唯一的标识符(DN,Distinguished Name), 以及一系列的属性(Attribute)。

不同的 IT 系统,允许访问的用户是不相同的。每个信息系统都有一个表达式,用来描述允许访问的用户。 这个表达式可以按照某一个属性的值作为条件来匹配用户,也可以用多个条件的逻辑组合来匹配用户。 小 C 被安排来实现这样一个算法,给定一个 IT 系统的匹配表达式,找到所有与之匹配的用户的 DN。

题目描述

为了简化该问题,我们约定,每个用户的 DN 是一个正整数,且不会重复。有若干种用户的属性,用正整数编号。 每个用户可以具有这些属性中的若干个,且每个属性只能有一个值。每个属性的值也是一个正整数。例如,假定有两个用户:用户 1 和用户 2, 他们的 DN 分别是 1 和 2。一共有 3 种属性。用户 1 具有属性 1 和属性 2,且属性 1 的值为 2,属性 2 的值为 3;但不具有属性 3。 用户 2 具有属性 2 和属性 3,且属性 2 的值为 3,属性 3 的值为 1;但不具有属性 1。如下表所示:

DN属性 1属性 2属性 3
123N/A
2N/A31

一个匹配表达式可以是一个属性的值,也可以是多个匹配表达式的逻辑组合。只匹配一个属性的值的表达式称为原子表达式, 原子表达式的形式为 <属性编号><操作符><属性值>。其中操作符有两种:断言与反断言。断言操作符为 :,表示匹配具有该属性且值与之相等的用户; 反断言操作符为 ~,表示匹配具有该属性且值与之不等的用户。例如,表达式 1:2 可以与上述用户 1 相匹配,但不能与用户 2 相匹配;而表达式 3~1 则不能与任何一个用户相匹配。

表达式可以进行逻辑组合,其语法是:<操作符>(表达式 1)(表达式 2)。其中操作符有两种:与(&)和或(|)。如果操作符为与, 则当且仅当两个表达式都与某一用户相匹配时,该表达式与该用户相匹配;如果操作符为或,则当且仅当两个表达式中至少有一个与某一用户相匹配时, 该表达式与该用户相匹配。例如,表达式 &(1:2)(2:3) 可以与用户 1 相匹配,但不能与用户 2 相匹配;而表达式 |(1:2)(3:1) 则可以与两个用户都相匹配。

形式化地,上述语法用 BNF 范式表示如下:

NON_ZERO_DIGIT =  "1" / "2" / "3" / "4" / 
                  "5" / "6" / "7" / "8" / "9"
DIGIT          =  "0" / NON_ZERO_DIGIT
NUMBER         =  NON_ZERO_DIGIT / (NON_ZERO_DIGIT DIGIT*)
ATTRIBUTE      =  NUMBER
VALUE          =  NUMBER
OPERATOR       =  ":" / "~"
BASE_EXPR      =  ATTRIBUTE OPERATOR VALUE
LOGIC          =  "&" / "|"
EXPR           =  BASE_EXPR / (LOGIC "(" EXPR ")" "(" EXPR ")")

EASY_EXPR      =  BASE_EXPR / 
                  (LOGIC "(" BASE_EXPR ")" "(" BASE_EXPR ")")

输入格式

从标准输入读入数据。

输入的第一行包含一个正整数 n,表示用户的数目。

接下来 n 行,每行包含空格分隔的若干个正整数,第一个正整数表示该用户的 DN,第二个正整数表示该用户具有的属性个数,此后的每两个正整数表示该用户具有的一个属性及其值。这些属性按照属性编号从小到大的顺序给出。

接下来一行包含一个正整数 m,表示匹配表达式的数目。

接下来 m 行,每行包含一个匹配表达式。

输出格式

输出到标准输出。

输出 m 行,每行包含零个或多个正整数,用空格分隔,表示与对应的匹配表达式相匹配的用户的 DN,由小到大排序。

思路

BASE_EXPR:

对于a:b运算,我们要找有a属性且属性值为b的用户,对于a~b运算,我们要找有a属性且属性值不为b的用户。因此我们可以用记录所有有a属性的用户map<int,set<int>> mp2,和所有有a属性且属性值为b的用户map<int,map<int,set<int>>> mp1。对于a:b运算,直接把mp1[a][b]中的值取出,对于a~b运算,把在mp2[a]且不在mp1[a][b]中的值取出。

EASY_EXPR/EXPR:

第一个字符肯定是&或|,后面是跟着两个表达式,可以将这两个表达式递归求解,直至分解成BASE_EXPR,再用并集或交集处理一下答案。

时间优化:

map用unordered_map,结果集合用vector或set,vector更快

代码

#include <iostream>
#include <algorithm>
#include <unordered_map>
#include <vector>
#include <set>
using namespace std;

unordered_map<int,unordered_map<int,set<int>>> mp1;
unordered_map<int,set<int>> mp2;

vector<int> strsolve(string str){//基本表达式的求解
    vector<int> ans;
    int a=0,b=0,i,j;
    for(i=0;isdigit(str[i]);i++) a=a*10+str[i]-'0';//属性编号
    for(j=i+1;j<str.size();j++) b=b*10+str[j]-'0';//属性值
    if(str[i]==':') {
    	for(auto it:mp1[a][b]) ans.push_back(it);
	}
    else{
        for(auto it:mp2[a])
            if(!mp1[a][b].count(it)) ans.push_back(it);
    }
    return ans;
}
vector<int> solve(string str){
    if(str[0]!='&'&&str[0]!='|') return strsolve(str);//基本表达式
    else{
        vector<int> ans,ans1,ans2;
        int p=2;
        for(int num=1;num;p++){//根据括号数量获得两个表达式
            if(str[p]==')') num--;
            if(str[p]=='(') num++;
        }
        ans1=solve(str.substr(2,p-3));
        ans2=solve(str.substr(p+1,str.size()-p-2));
        if(str[0]=='&'){//交集
            set_intersection(ans1.begin(),ans1.end(),ans2.begin(),ans2.end(),inserter(ans,ans.begin()));
        }
        else{//并集
            set_union(ans1.begin(),ans1.end(),ans2.begin(),ans2.end(),inserter(ans,ans.begin()));
        }
        return ans;
    }
}

int main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    int n,m;
    cin>>n;
    while(n--){
        int id,k,a,b;
        cin>>id>>k;
        while(k--){
            cin>>a>>b;
            mp1[a][b].insert(id);
            mp2[a].insert(id);
        }
    }
    cin>>m;
    while(m--){
        string s; cin>>s;
        vector<int> ans=solve(s);
        for(auto it:ans) cout<<it<<" ";
        cout<<endl;
    }
    return 0;
}

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值