第29次CCF计算机软件能力认证

发布时间 2023-03-29 18:16:15作者: ~Lanly~

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范围内(包含lr,下同)的一段连续地址块。若该范围未被分配,或者已部分分配给用户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;
}