202303 CSP认证 | 田地丈量 垦田计划 LDAP (bitset)

田地丈量

#include<bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    
    int n, x, y; cin >> n >> x >> y;
    int sum = 0;
    while(n --){
        int x1, y1, x2, y2;
        cin >> x1 >> y1 >> x2 >> y2;
        if(x1 >= x) continue;
        if(x2 <= 0) continue;
        if(y1 >= y) continue;
        if(y2 <= 0) continue;
        
        int a = max(0, x1), b = min(x, x2);
        int c = max(0, y1), d = min(y, y2);
        
        sum += ((b - a) * (d - c));
    }
    cout << sum;
    return 0;
}

垦田计划
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
unordered_map<int, int> source;
bool cmp(int x, int y)
{
    return x > y;
}
int main()
{
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);

    int n, m, k; cin >> n >> m >> k;
    vector<int> vec;
    for(int i = 1;i <= n;i ++){
        int t, c;
        cin >> t >> c;
        source[t] += c;   //计算每个单位需要的总资源
        vec.push_back(t);
    }
    sort(vec.begin(), vec.end(), cmp);
    vec.erase(unique(vec.begin(), vec.end()), vec.end());
    bool flag = false;
    for(int i = vec[0]; i >= k + 1; i --){
        if(m >= source[i]){
            m -= source[i];
            source[i - 1] += source[i];
        }
        else { cout << i; flag = true; break; }
    }
    if(!flag) cout << k;
    return 0;
}


LDAP
好好好,难度直线上升,是一道又有了字符串处理味道的第三题
第一把写官网40分,acwing TLE且只通过了一道数据…本文是自己这题奋斗过程 的一个记录

先贴个40分的代码:
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;   // key and value pair
int n;

unordered_map<int, unordered_map<string, string> > user;  //每个用户对应一个key and value的映射表
unordered_map<string, unordered_set<int> > key_user;  //i作为key, 表示哪些用户具有属性i

//处理一个基本表达式,检查基本表达式与dn的用户是否匹配
set<int> base_expr(string expr)
{
    int i = 0;
    while(expr[i] != ':' && expr[i] != '~') i ++;
    string arr = expr.substr(0, i);
    string value = expr.substr(i + 1, expr.size() - i - 1);

    set<int> res;

    unordered_set<int> users = key_user[arr];
    if(expr[i] == ':'){
        for(auto x : users){   //遍历每一个用户
            if(user[x][arr] == value){
                res.insert(x);
            }
        }
    }else{
        for(auto x : users){
            if(user[x][arr] != value){
                res.insert(x);
            }
        }
    }

    return res;
}

//处理一个常规的表达式
set<int> handle(string expr)
{
    set<int> ans;

    //括号要成对取出
    if(expr[0] == '&' || expr[0] == '|'){
        stack<char> s; int i = 2;
        set<int> res1, res2;

        s.push('(');
        int cnt = 1;
        while(!s.empty()){
            while(expr[i] != '(' && expr[i] != ')') i ++;
            if(expr[i] == '(') { s.push('('); cnt ++; }  //入栈并进行计数
            else if(expr[i] == ')') s.pop(); //弹出一个栈顶的
            i ++;
        }
        //此时第一个括号提取完毕
        string expr1 = expr.substr(2, i - 3);
        string expr2 = expr.substr(i + 1, expr.size() - i - 2);
        //cout << "两个表达式分别为:\n" << expr1 <<"\n" << expr2 <<"\n";

        if(cnt == 1){   //此时是一个简单表达式
            res1 = base_expr(expr1);
            res2 = base_expr(expr2);
        }
        else{
            res1 = handle(expr1);
            res2 = handle(expr2);
        }


        if(expr[0] == '&'){  //取两个集合的交集
            for(auto x : res1){   //遍历一个集合就可以了
                if(res2.count(x)) ans.insert(x);
            }

        }else{   //取两个集合的并集
            for(auto x : res1) ans.insert(x);
            for(auto x : res2) ans.insert(x);
        }

    }

    else { //若要处理的输入就是一个基本表达式
        ans = base_expr(expr);
    }

    return ans;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n;
    for(int i = 0;i < n;i ++){
        int dn, x;
        string arr, val;
        cin >> dn >> x;
        while(x --){
            cin >> arr >> val;
            key_user[arr].insert(dn);  //dn具有属性arr
            user[dn][arr] = val;  //dn具有属性arr,且value = val
        }
    }
    int m; cin >> m;
    string line;
    while(m --){
        cin >> line;
        set<int> ans = handle(line);
        for(auto x : ans){
            cout << x <<' ';
        }
        cout << "\n";
    }
    return 0;
}

秉持着自己的逻辑应该没错但是官网是40分错误的判断,我找了几分代码比对着修改了一下,找到了一个逻辑错误。

handle函数中我统计了一个cnt用于控制当括号里面的是基础表达式的时候就直接调用base_expr函数,问题就出在这里。
· 第一个问题:我只统计了第一个括号的层数,我默认这两个括号的层级是一样的。这样会导致错误,也就是当前一个括号里是基本表达式而第二个括号里面并不是的时候,此时base_expr并不能解决这样的表达式
· 第二个问题:这里也不算会直接导致错误的原因。在handle函数中,如果发现不是一个逻辑表达式的话,其实有调用base_expr函数的。因此不用重复写,直接一直迭代向下调用handle函数即可。

修改后代码运行超时,逻辑问题变优化问题了,先放个70分的代码(代码几乎没变,就是handle函数体内部改变)
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;   // key and value pair
int n;

unordered_map<int, unordered_map<string, string> > user;  //每个用户对应一个key and value的映射表
unordered_map<string, unordered_set<int> > key_user;  //i作为key, 表示哪些用户具有属性i

//处理一个基本表达式,检查基本表达式与dn的用户是否匹配
set<int> base_expr(string expr)
{
    int i = 0;
    while(expr[i] != ':' && expr[i] != '~') i ++;
    string arr = expr.substr(0, i);
    string value = expr.substr(i + 1, expr.size() - i - 1);

    set<int> res;

    unordered_set<int> users = key_user[arr];
    if(expr[i] == ':'){
        for(auto x : users){   //遍历每一个用户
            if(user[x][arr] == value){
                res.insert(x);
            }
        }
    }else{
        for(auto x : users){
            if(user[x][arr] != value){
                res.insert(x);
            }
        }
    }

    return res;
}

//处理一个常规的表达式
set<int> handle(string expr)
{
    set<int> ans;

    //括号要成对取出
    if(expr[0] == '&' || expr[0] == '|'){
        stack<char> s; int i = 2;
        set<int> res1, res2;

        s.push('(');
        while(!s.empty()){
            while(expr[i] != '(' && expr[i] != ')') i ++;
            if(expr[i] == '(')  s.push('(');    //入栈并进行计数
            else if(expr[i] == ')') s.pop(); //弹出一个栈顶的
            i ++;
        }
        //此时第一个括号提取完毕
        string expr1 = expr.substr(2, i - 3);
        string expr2 = expr.substr(i + 1, expr.size() - i - 2);
        //cout << "两个表达式分别为:\n" << expr1 <<"\n" << expr2 <<"\n";

        res1 = handle(expr1);
        res2 = handle(expr2);

        if(expr[0] == '&'){  //取两个集合的交集
            for(auto x : res1){   //遍历一个集合就可以了
                if(res2.count(x)) ans.insert(x);
            }

        }else{   //取两个集合的并集
            for(auto x : res1) ans.insert(x);
            for(auto x : res2) ans.insert(x);
        }

    }

    else { //若要处理的输入就是一个基本表达式
        ans = base_expr(expr);
    }

    return ans;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin >> n;
    for(int i = 0;i < n;i ++){
        int dn, x;
        string arr, val;
        cin >> dn >> x;
        while(x --){
            cin >> arr >> val;
            key_user[arr].insert(dn);  //dn具有属性arr
            user[dn][arr] = val;  //dn具有属性arr,且value = val
        }
    }
    int m; cin >> m;
    string line;
    while(m --){
        cin >> line;
        set<int> ans = handle(line);
        for(auto x : ans){
            cout << x <<' ';
        }
        cout << "\n";
    }
    return 0;
}

在找满分代码的过程中发现大家的思路总体来说差不多,不同的是在存储中的数据结构设置。
最终修改的满分答案参考了这位博主的:bitset优化,可过

总体来说思路其实是一模一样的,不同的地方在于用了bitset
大家可以去搜一下bitset的用法,其本质上就是连续的01串,可以用bitset<N> s来定义,其中 N 指定大小。在本题中使用bitset的原理是为了让集合的并运算和交运算更为简洁

可以这么理解本题中的bitset
它就像一个one-hot编码,对应编号为1,则说明该用户存在(于答案中)
在原有的存储方式中,当我对每一个表达式做判断时,我找到符合条件的用户,并存储这些用户的id,也就是dn的值;最后在逻辑运算中,根据逻辑符号的不同,涉及对两个集合的交操作和并操作。
当修改为bitset后有这样一些优化:

  1. 合理性。为什么可以用bitset来做,我认为哈。每位用户的id不同,id的范围到1e9,但是n的范围小多了,最多就是2000个。也就是说我可以不用dn去定位到每一个用户,我可以用他们输入的位置编号来定位用户,在此基础上我只需要将他们的dn存储起来,在输出时取得即可。这样大大减少了范围,这个数量级下我可以用一维数组来存储。例如在本题中,其实我没有用dn去定位每一个用户,而是输入循环中for(int i = 1;i <= n;i ++中的i来定位。pos[i] = 1,意味着i用户符合要求,此时输出user_id[i]即可
  2. 计算更快。在计算交和并操作时,此时只涉及到按位运算,可想而知速度会大大加快;少了集合的遍历,而且位数也不高,就2000位

努力了一下午我终于…看到了100分…
在这里插入图片描述
贴一个修改后的满分代码!!!

//use bitset for better set_union and set_intersection
#include<bits/stdc++.h>
using namespace std;
typedef pair<string, string> PSS;
const int N = 3000;
int n, user[N];  //to store user id(dn)
unordered_map<string, unordered_set<int> > hask;  //哪个用户具有特定的key
map<PSS, unordered_set<int> > haskv;   //哪个用户具有某个特定的key-value对

bitset<N> handle(string expr)
{
    bitset<N> ans;   //最终的答案,若ans[i], 也即user[i]在返回的结果中

    if(expr[0] == '&' || expr[0] == '|'){  //如果为逻辑表达式
        stack<char> s; s.push('(');
        int i = 1;
        while(!s.empty()){  //仍然用栈的方法实现括号匹配
            i ++;
            if(expr[i] == '(') s.push('(');
            else if(expr[i] == ')') s.pop();
        }
        //出循环时expr[i] == ),为第一个括号的最右端

        string expr1 = expr.substr(2, i - 2);
        string expr2 = expr.substr(i + 2, expr.size() - i - 3);

        bitset<N> res1 = handle(expr1);
        bitset<N> res2 = handle(expr2);

        if(expr[0] == '&') ans = res1 & res2;
        else ans = res1 | res2;
        return ans;
    }

    else{  //如果为基础表达式
        int i = 0;
        while(expr[i] != ':' && expr[i] != '~') i ++;
        string key = expr.substr(0, i);
        string value = expr.substr(i + 1, expr.size() - i - 1);

        bool flag = false;  //该操作是否为反断言
        if(expr[i] == '~') flag = true;

        for(auto x : haskv[{key,value}]){
            ans[x] = 1;   //x所对的位置应该是1,也即答案中存在该dn
        }

        if(flag){  //如果为反断言
            for(auto x : hask[key]){
                ans.flip(x);   //flip是取反操作
                //对于拥有该key的,目前只有value值匹配的ans位是为一的
                //现在进行取反操作,这会导致刚刚所取值1的,也就是value值匹配的,都修改为0
                //而其余具有该key的(但value值不相等的), 则会置为1.此时完成了我们的要求
            }
        }

    }
    return ans;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin >> n;
    for(int i = 1;i <= n;i ++){
        int dn; cin >> dn;
        user[i] = dn;
        int k; cin >> k;
        while(k --){
            string key, val; cin >> key >> val;
            hask[key].insert(i);
            haskv[{key, val}].insert(i);
        }
    }

    int m; cin >> m;
    string line;

    while(m --){
        cin >> line;
        bitset<N> v = handle(line);
        vector<int> ans;  //存储答案

        for(int i = 1;i <= n;i ++){
            if(v[i]) ans.push_back(user[i]);
        }
        sort(ans.begin(),ans.end());  //排序
        for(auto x : ans){
            cout << x << ' ';
        }
        cout << "\n";
    }
    return 0;
}

与源代码的不同,第一个我保留了我最初的想法:用栈来实现括号匹配
第二个就是,在本题中的key-value我认为没有转化为int类型的必要性,如果只是匹配的话,直接用string存储即可,少了后续的一个转化步骤。
其他的部分是相同的。

有一个bug我改了特别特别久…提交的时候一直显示段错误,其实这也是我对该方法的不理解(最开始)
在代码的71、72行,我最开始的书写是:

hask[key].insert(dn);
haskv[{key, val}].insert(dn);

也就是我最开始仍然在用dn去标识每位用户,这个会导致代码的45行以及50行出错,因为dn的范围远大于n;感觉使用bitset就是在空间和时间上的双重优化。空间上的优化就是一维向量标识压缩了多个数量级(从9到3次方),而时间上的优化主要就是在并操作和交操作上。当然这都是基于0-1向量可表示用户的基础上

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值