题目背景
西西艾弗岛运营公司是一家负责维护和运营岛上基础设施的大型企业,拥有数千名员工。公司内有很多 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 |
---|---|---|---|
1 | 2 | 3 | N/A |
2 | N/A | 3 | 1 |
一个匹配表达式可以是一个属性的值,也可以是多个匹配表达式的逻辑组合。只匹配一个属性的值的表达式称为原子表达式,原子表达式的形式为 <属性编号><操作符><属性值>
。其中操作符有两种:断言与反断言。断言操作符为 :
,表示匹配具有该属性且值与之相等的用户;反断言操作符为 ~
,表示匹配具有该属性且值与之不等的用户。例如,表达式 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 ")")
None
输入格式
从标准输入读入数据。
输入的第一行包含一个正整数 �,表示用户的数目。
接下来 � 行,每行包含空格分隔的若干个正整数,第一个正整数表示该用户的 DN,第二个正整数表示该用户具有的属性个数,此后的每两个正整数表示该用户具有的一个属性及其值。这些属性按照属性编号从小到大的顺序给出。
接下来一行包含一个正整数 �,表示匹配表达式的数目。
接下来 � 行,每行包含一个匹配表达式。
输出格式
输出到标准输出。
输出 � 行,每行包含零个或多个正整数,用空格分隔,表示与对应的匹配表达式相匹配的用户的 DN,由小到大排序。
解题思路
使用编译原理学习的思想
题目就是简单定义了几个文法,使用简单的递归下降即可解决。但对于并和交操作不要使用c++ set<int>进行,大概率超时。
将所有dn集合转化为长度为n的bitset进行并和交操作,使用&和|运算符,同时避免了繁琐的set操作,可以加速。
结果:
源代码:
#include<bits/stdc++.h>
using namespace std;
const int MAX_N = 2510;
typedef bitset<MAX_N> bs;
//用于对应二进制位和dn
map<int,int> dn_bit;
map<int,int> bit_dn;
//定义数据结构,每个属性使用一个map存储信息,对于不同的取值使用一个bitset来记录有哪些dn
struct ATRTree
{
int n=0;
map<int, bs> data;
bs dns; //记录这个属性共出现了哪些dn,用于处理'~'操作符
void insert(int v, int i) {
data[v].set(i);
dns.set(i);
n++;
}
};
bool is_num(char c) {
return c>=48 && c<=57;
}
//存储所有的属性取值的dn有哪些
map<int,ATRTree> trees;
int parseNUM(string& s, int a, int* pos);
bs parseBASE_EXPR(string& s, int a, int* pos_tmp);
bs parseLOGIC(string &s, int a, int* pos_tmp);
bs parseEXPR(string &s, int a, int* pos);
int parseNUM(string& s, int a, int* pos) {
int end = a;
while (is_num(s[end])) {
end++;
}
*pos = end;
return stoi(s.substr(a, end - a));
}
bs parseBASE_EXPR(string& s, int a, int* pos_tmp) {
bs ans;
int pos1;
int atr = parseNUM(s, a, &pos1);
int pos2;
int v = parseNUM(s, pos1+1, &pos2);
*pos_tmp = pos2;
if(s[pos1]==':') {
if(trees[atr].n>0) {
ans = trees[atr].data[v];
}
}
else {
ans = trees[atr].dns;
ans = ans ^ trees[atr].data[v];
}
return ans;
}
bs parseLOGIC(string &s, int a, int* pos_tmp) {
bs ans;
int pos1;
bs A = parseEXPR(s, a+2, &pos1);
int pos2;
bs B = parseEXPR(s, pos1+2, &pos2);
*pos_tmp = pos2 + 1;
if(s[a]=='&') {
ans = A & B;
}
else {
ans = A | B;
}
return ans;
}
bs parseEXPR(string &s, int a, int* pos) {
if(is_num(s[a])) {
return parseBASE_EXPR(s, a, pos);
}
else {
return parseLOGIC(s, a, pos);
}
}
void print_set(bs& b, int n) {
set<int> a;
for(int i=0;i<n;i++) {
if(b[i]==1) {
a.insert(bit_dn[i]);
}
}
for(auto i = a.begin(); i!=a.end(); i++) {
if(i==a.begin()) {
cout<<*i;
}
else {
cout<<' '<<*i;
}
}
cout<<'\n';
}
int main() {
int n,m,t,d;
cin>>n;
set<int> all_dn;
for(int i=0;i<n;i++) {
cin>>d>>t;
dn_bit[d] = i;
bit_dn[i] = d;
int atr, v;
for(int j=0;j<t;j++) {
cin>>atr>>v;
trees[atr].insert(v, i);
}
all_dn.insert(d);
}
cin>>m;
string s;
getline(cin, s);
for(int i=0;i<m;i++) {
getline(cin, s);
int a;
//解析字符串
bs S = parseEXPR(s, 0, &a);
print_set(S, n);
}
return 0;
}