第29次CCF计算机软件能力认证
100+100+100+100+60=460
田地丈量
题目大意
二维平面,给定一个矩阵区域\(a\)和若干个互不相交的矩形区域组 \(b\)。
问 \(a\)和 \(b\)的相交面积和。
解题思路
因为\(b\)中矩形互不相交,因此可以分别计算与 \(a\)的面积交,相加即是答案,不会有重。
神奇的代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, a, b;
cin >> n >> a >> b;
LL ans = 0;
auto calc = [&](int x1, int y1, int x2, int y2){
int l = max(x1, 0);
int r = max(y1, 0);
int L = min(a, x2);
int R = min(b, y2);
return 1ll * max(L - l, 0) * max(R - r, 0);
};
for(int i = 1; i <= n; ++ i){
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
ans += calc(x1, y1, x2, y2);
}
cout << ans << '\n';
return 0;
}
垦田计划
题目大意
给定一个\(n\)个数的数组\(a\),对于每个 数\(a_i\),可花 \(c_i\)的代价令其减一。但每个数最低可减到 \(k\)。
问在不花费超过 \(m\)的代价,该数组的最大值的最小值是多少。
解题思路
经典最大值最小,二分该值后判断一下代价是否超过\(m\)即可。
神奇的代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, m, k;
cin >> n >> m >> k;
vector<array<int, 2>> a(n);
int l = k - 1, r = 0;
for(auto &i : a){
cin >> i[0] >> i[1];
r = max(r, i[0]);
}
auto check = [&](int x){
LL sum = 0;
for(auto &i : a){
sum += 1ll * max(0, i[0] - x) * i[1];
}
return sum <= m;
};
while(l + 1 < r){
int mid = (l + r) >> 1;
if (check(mid)){
r = mid;
}else{
l = mid;
}
}
cout << r << '\n';
return 0;
}
LDAP
题目大意
给定\(n\)个用户,用户有 唯一的\(userID\),以及若干个属性。
现给定若干个查询指令,要求从小到大输出符合条件的 \(userID\)。
查询指令的\(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 ")")
比如1:2
表示查询属性1
值为2
的用户,1~2
表示查询有属性1
但属性1
值不为2
的用户。
然后以上可以用逻辑&
和|
组合,即&(1:2)(2:3)
表示查询属性1
值为2
且属性2
值为3
的用户,|(1:2)(3:1)
表示查询属性1
值为2
或属性3
值为1
的用户。
解题思路
按照BNF
写一个递归下降分析器就好了,一个简单的parser
。
每个判断语句的结果可以用一个bitset
储存起来,这样就可以直接&
和|
了。
神奇的代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2500;
unordered_map<int, int> user[N];
int n, q;
bool islogic(char s){
return s == '&' || s == '|';
}
int find_rb(const string &s, int l){
int cnt = 0;
assert(s[l] == '(');
for(int i = l; i < int(s.size()); ++ i){
if (s[i] == '(')
++ cnt;
else if (s[i] == ')')
-- cnt;
if (cnt == 0)
return i;
}
assert(0);
return -1;
}
int find_op(const string &s, int l){
for(int i = l; i < int(s.size()); ++ i){
if (s[i] == ':' || s[i] == '~')
return i;
}
assert(0);
return -1;
}
bitset<N> parse_basic(const string &s, int l, int r){
int op = find_op(s, l);
int attr = atoi(s.substr(l, op - l).c_str());
int value = atoi(s.substr(op + 1, r - op).c_str());
bitset<N> result;
if (s[op] == ':'){
for(int i = 0; i < n; ++ i){
if (user[i].find(attr) != user[i].end() && user[i][attr] == value)
result.set(i);
}
}else{
for(int i = 0; i < n; ++ i){
if (user[i].find(attr) != user[i].end() && user[i][attr] != value)
result.set(i);
}
}
return result;
}
bitset<N> parse_expr(const string &s, int l, int r){
if (islogic(s[l])){
int rb = find_rb(s, l + 1);
auto res1 = parse_expr(s, l + 2, rb - 1);
auto res2 = parse_expr(s, rb + 2, r - 1);
if (s[l] == '|')
return res1 | res2;
else
return res1 & res2;
}else{
return parse_basic(s, l, r);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n;
for(int i = 0; i < n; ++ i){
cin >> user[i][0];
int x;
cin >> x;
while(x--){
int k, v;
cin >> k >> v;
user[i][k] = v;
}
}
cin >> q;
while(q--){
string s;
cin >> s;
auto result = parse_expr(s, 0, s.size() - 1);
vector<int> ans;
for(int i = 0; i < n; ++ i){
if (result[i])
ans.push_back(user[i][0]);
}
sort(ans.begin(), ans.end());
for(auto &i : ans)
cout << i << ' ';
cout << '\n';
}
return 0;
}
星际网络II
题目大意
你需要维护由\(n\)位二进制数组成的网络地址的分配。有以下\(3\)种操作
1 id l r
:表示用户id
申请地址在l ~ r
范围内(包含l
和r
,下同)的一段连续地址块。若该范围未被分配,或者已部分分配给用户id
则成功,否则(已全部分配给用户id
或部分分配给其他用户)失败。2 s
:检查地址s
分配给哪个用户,未分配则输出0
3 l r
:检查地址范围l ~ r
是否完整地分配给某个用户,是则输出其id
,否则输出0
。
解题思路
典型的区间赋值+查询,如果地址不大,可以用柯朵莉树直接维护。
但由于n
高达512
位,不能用一个数表示,直接字符串比较会有\(128\)的常数,因此就每32
位压成一个unsigned int
,用16个数表示一个地址,这样常数只有16
,然后就用柯朵莉树维护这个即可。
具体实现时在\(n=16\)这个较小的数据 wa
了,赛场上看不出哪里错,但因为\(n\)比较小,于是就单独写了这部分数据的简单化版,不知道改了哪里就过了(
神奇的代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
constexpr int duan = 2;
constexpr int wei = 16;
int n, q;
struct Address{
unsigned int val[wei];
Address(){
memset(val, 0, sizeof(val));
}
bool operator <(const Address& o) const {
for(int i = wei - 1; i >= 0; -- i){
if (val[i] != o.val[i])
return val[i] < o.val[i];
}
return false;
}
bool operator <=(const Address& o) const {
for(int i = wei - 1; i >= 0; -- i){
if (val[i] != o.val[i])
return val[i] < o.val[i];
}
return true;
}
bool operator !=(const Address& o) const {
for(int i = wei - 1; i >= 0; -- i){
if (val[i] != o.val[i])
return true;
}
return false;
}
bool operator ==(const Address& o) const {
for(int i = wei - 1; i >= 0; -- i){
if (val[i] != o.val[i])
return false;
}
return true;
}
Address& operator ++(){
int pos = 0;
while(true){
val[pos] ++;
if (val[pos] == 0){
++ pos;
}else
break;
}
return *this;
}
Address& operator --(){
int pos = 0;
while(true){
if (val[pos] == 0){
val[pos] --;
++ pos;
}else{
val[pos] --;
break;
}
}
return *this;
}
};
struct area{
Address l, r;
int own;
bool operator <(const area& o) const{
return l < o.l;
}
bool operator <=(const area& o) const{
return l <= o.l;
}
};
unsigned int tr(char s){
if (s >= '0' && s <= '9')
return s - '0';
else
return s - 'a' + 10;
}
unsigned int trans_int(const string& s, int l, int r){
unsigned int qwq = 0;
for(int i = l; i < r; ++ i){
qwq = qwq * 16 + tr(s[i]);
}
return qwq;
}
Address trans(const string& s){
Address res;
vector<unsigned int> tmp;
for(int i = 0; i < int(s.size()); i += 4){
tmp.push_back(trans_int(s, i, i + 4));
++ i;
}
int cnt = 0;
unsigned int val = 0;
int pos = 0;
unsigned int base = 1;
for(int i = tmp.size() - 1; i >= 0; -- i){
val = val + tmp[i] * base;
++ cnt;
base <<= 16;
if (cnt == duan){
res.val[pos] = val;
val = 0;
cnt = 0;
base = 1;
++ pos;
}
}
if (cnt)
res.val[pos] = val;
return res;
}
set<area> qq;
set<pair<pair<int, int>, int>> small;
const string zero = "0000";
const string maxx = "ffff";
Address max_address, min_address;
int bigg;
bool add(int id, const Address& l, const Address& r){
auto L = prev(qq.upper_bound({l, max_address})), R = qq.upper_bound({r, max_address});
int count = 0;
bool allmy = true;
for(auto i = L; i != R; i = next(i)){
if (i->own != 0 && i->own != id)
return false;
++ count;
allmy &= (i->own == id);
}
if (allmy)
return false;
auto LST = *L, RST = *prev(R);
for(auto it = L; count > 0; it = qq.erase(it), -- count);
Address insertL = l, insertR = r;
if (LST.own != id){
if (LST.l != l){
auto newR = l;
-- newR;
LST.r = newR;
qq.insert(LST);
}
}else{
insertL = LST.l;
}
if (RST.own != id){
if (RST.r != r){
auto newL = r;
++ newL;
RST.l = newL;
qq.insert(RST);
}
}else{
insertR = RST.r;
}
qq.insert({insertL, insertR, id});
return true;
}
int find(const Address& pos){
auto L = prev(qq.upper_bound({pos, max_address}));
return L->own;
}
int check(const Address&l, const Address&r){
auto L = prev(qq.upper_bound({l, max_address})), R = prev(qq.upper_bound({r, max_address}));
if (L == R)
return L->own;
else
return 0;
}
bool addsmall(int id, const int& l, const int& r){
auto L = prev(small.upper_bound({{l, bigg}, 0})), R = small.upper_bound({{r, bigg}, 0});
int count = 0;
bool allmy = true;
for(auto i = L; i != R; i = next(i)){
if (i->second != 0 && i->second != id)
return false;
++ count;
allmy &= (i->second == id);
}
if (allmy)
return false;
auto LST = *L, RST = *prev(R);
for(auto it = L; count > 0; it = small.erase(it), -- count);
auto insertL = l, insertR = r;
if (LST.second != id){
if (LST.first.first != l){
auto newR = l;
-- newR;
LST.first.second = newR;
small.insert(LST);
}
}else{
insertL = min(insertL, LST.first.first);
}
if (RST.second != id){
if (RST.first.second != r){
auto newL = r;
++ newL;
RST.first.first = newL;
small.insert(RST);
}
}else{
insertR = max(insertR, RST.first.second);
}
small.insert({{insertL, insertR}, id});
return true;
}
int findsmall(const int& pos){
auto L = prev(small.upper_bound({{pos, bigg}, 0}));
return L->second;
}
int checksmall(const int&l, const int&r){
auto L = prev(small.upper_bound({{l, bigg}, 0})), R = small.upper_bound({{r, bigg}, 0});
for(auto it = L; it != R; it = next(it))
if (it->second != L->second)
return 0;
return L->second;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> q;
if (n == 16){
bigg = (1 << n);
small.insert({{0, bigg - 1}, 0});
while(q--){
int op;
cin >> op;
if (op == 1){
int id;
string l, r;
cin >> id >> l >> r;
auto L = trans_int(l, 0, 4), R = trans_int(r, 0, 4);
if (addsmall(id, L, R))
cout << "YES" << '\n';
else
cout << "NO" << '\n';
}else if (op == 2){
string s;
cin >> s;
auto S = trans_int(s, 0, 4);
int res = findsmall(S);
cout << res << '\n';
}else {
string l, r;
cin >> l >> r;
auto L = trans_int(l, 0, 4), R = trans_int(r, 0, 4);
int res = checksmall(L, R);
cout << res << '\n';
}
}
}else{
string st, ed;
for(int i = 0, up = n / 16; i < up; ++ i){
st += zero;
ed += maxx;
if (i != up - 1){
st += ":";
ed += ":";
}
}
min_address = trans(st);
max_address = trans(ed);
area tmp{min_address, max_address, 0};
qq.insert(tmp);
while(q--){
int op;
cin >> op;
if (op == 1){
int id;
string l, r;
cin >> id >> l >> r;
auto L = trans(l), R = trans(r);
if (add(id, L, R))
cout << "YES" << '\n';
else
cout << "NO" << '\n';
}else if (op == 2){
string s;
cin >> s;
auto S = trans(s);
int res = find(S);
cout << res << '\n';
}else {
string l, r;
cin >> l >> r;
auto L = trans(l), R = trans(r);
int res = check(L, R);
cout << res << '\n';
}
}
}
return 0;
}
另解是地址离散化+权值线段树维护分配。
施肥
题目大意
一维数轴,给定\(m\)条线段,问有多少个区间\([l,r]\),满足可以选择若干条线段,恰好覆盖该区间。
解题思路
设\(ok[i][j]\)表示能否恰好覆盖区间 \([i,j]\),然后就枚举左端点在\(i\)的线段(设右端点为 \(r\)),那 \(ok[i][j] = ok[i][j]\ |\ ok[i + 1][j]\ |\ ok[i + 2][j]\ |\ \cdots\ |\ ok[r - 1][j]\ |\ ok[r][j]\ |\ ok[r + 1][j]\)
最后答案就是\(ok\)数组中为 \(true\)个数。
时间复杂度是 \(O(n^2 + nm)\),水了60
分。
神奇的代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, m;
cin >> n >> m;
vector<vector<int>> ok(n, vector<int>(n, 0));
vector<array<int, 2>> seg(m);
vector<vector<int>> edge(n);
for(int i = 0; i < m; ++ i){
cin >> seg[i][0] >> seg[i][1];
-- seg[i][0];
-- seg[i][1];
edge[seg[i][0]].push_back(i);
ok[seg[i][0]][seg[i][1]] = 1;
}
for(int i = 0; i < n; ++ i)
sort(edge[i].begin(), edge[i].end(), [&](int x, int y){
return seg[x][1] < seg[y][1];
});
for(int i = n - 1; i >= 0; -- i)
for(int j = i; j < n; ++ j){
for(auto &s : edge[i]){
if (seg[s][1] > j)
break;
for(int k = seg[s][0]; k <= seg[s][1] + 1 && k < n; ++ k){
ok[i][j] |= ok[k][j];
if (ok[i][j])
break;
}
if (ok[i][j])
break;
}
}
LL ans = 0;
for(int i = 0; i < n; ++ i)
for(int j = i; j < n; ++ j)
ans += ok[i][j];
cout << ans << '\n';
return 0;
}