CCF-CSP第38次认证第三题——消息解码(满分题解)

官网链接TUOJ

题解

快写吐了,一堆小细节。

除2和乘2可以直接使用位运算。

总流程

输入:n

  1. 复杂消息:
    1. 代号:先转化成数字,再根据数字表示转化为原字符。
    2. 12位散列值:转化为数字,根据哈希判断是否存在对应字符串。
    3. 收发标志:决定发送方和接收方。
    4. 将代号转化为12位和25位散列值(先计算字符的数字表示,再计算其散列值),存入哈希表(这一步要最后做)。
  2. 简单消息
    1. 接收方代号:
      1. 大于2的25次方,先减去2的25次方,再根据短数字表示计算原字符串(短字符很坑,需要特殊考虑“00AXX”的情况,因为第一位为0表示空格,第二位为0表示“0”,后几位同理)
      2. 小于2的25次方,转化为数字,根据哈希判断是否存在对应字符串。
      3. 将代号转化为12位和25位散列值,存入哈希表(这一步要先做)。
    2. 发送方代号:同上。
    3. 位置编号:转化为数字,注意为0的时候不需要输出。

相关函数

qmi:快速幂,计算a的b次方,直接循环计算应该也可以。

change:将01字符串转化为整数。

sh:short短数字表示,将数字转化为字符串。

ha:计算散列值。

sanlie:根据散列值进行查询。

num:计算出字符的数字表示。

solve0:简单消息处理主函数。

solve11:复杂消息代号处理主函数。

solve12:复杂消息12位散列值处理主函数。

代码

有注释版本(ai注释)

#include<bits/stdc++.h>
using namespace std;

typedef unsigned long long ll;

int n;                     // 数据组数
ll c25 = 33554432;         // 2^25,用来判断 28 位 01 串是否 >= 2^25
string dat;                // 每次读入的一整行字符串
unordered_map<ll,string> mp12, mp25;   // 哈希表:key 为 12/25 位哈希值,value 为压缩后的串

/* 快速幂:base^m,用来算 38^k */
ll qmi(ll base,ll m){
    ll res = 1;
    for(; m; m /= 2, base *= base)
        if(m & 1) res *= base;
    return res;
}

/* 把 01 串转成十进制整数 */
ll change(string s){
    ll res = 0, base = 1;
    for(int i = s.size() - 1; i >= 0; i--){
        if(s[i] == '1') res += base;
        base *= 2;
    }
    return res;
}

/* 把整数 hash 映射成 3~5 位的“短码”:先字母后数字再混合 */
string sh(ll hash){
    string res;
    ll op;
    /* 3 个大写字母 */
    for(int i = 0; i < 3; i++){
        op = hash % 26;
        res += (char)(op + 'A');
        hash /= 26;
    }
    /* 1 位数字 */
    if(hash > 0){
        op = hash % 10;
        res += (char)(op + '0');
        hash /= 10;
    }else res += '0';
    /* 1 位 0-9/A-Z */
    if(hash > 0){
        op = hash % 36;
        if(op <= 9) res += (char)(op + '0');
        else        res += (char)(op + 'A' - 10);
        hash /= 36;
    }else res += '0';
    /* 可能再补 1 位 */
    if(hash > 0){
        op = hash;
        if(op != 0){
            if(op <= 10) res += (char)(op + '0' - 1);
            else         res += (char)(op - 11 + 'A');
        }
    }
    reverse(res.begin(), res.end());
    return res;
}

/* 把一个整数 hash 再映射到 0~(2^base-1) 的更小哈希值(高位截取)*/
ll ha(ll op, ll base){
    return op * 47055833459ULL >> (64 - base);
}

/* 根据哈希值去表里查,找不到返回 "###" */
string sanlie(ll hash, ll base){
    if(base == 25){
        if(mp25.count(hash)) return "#" + mp25[hash];
        else return "###";
    }else{
        if(mp12.count(hash)) return "#" + mp12[hash];
        else return "###";
    }
}

/* 把 10 位 38 进制串(含数字、字母、_)再转回整数 */
ll num(string s){
    ll res = 0, base = qmi(38, 10);
    for(int i = 0; i < s.size(); i++){
        if(isdigit(s[i]))         res += base * (s[i] - '0' + 1);
        else if(s[i] == '_')      res += base * 37;
        else                      res += base * (s[i] - 'A' + 11);
        base /= 38;
    }
    return res;
}

/* 28 位 01 串 -> 压缩串 */
string solve0(string s){
    ll hash = change(s);
    if(hash >= c25){                       // >= 2^25 走 11 位
        hash -= c25;
        string s1 = sh(hash);              // 先映射成 3~5 位短码
        hash = num(s1);                    // 再转成 38 进制整数
        mp12[ha(hash, 12)] = s1;           // 填两张哈希表
        mp25[ha(hash, 25)] = s1;
        return s1;
    }else{
        return sanlie(hash, 25);           // 直接查 25 位哈希
    }
}

/* 58 位 01 串 -> 11 位 38 进制串(带 _) */
string solve11(string s){
    ll x = change(s), op;
    string res;
    for(int i = 0; i < 11; i++){
        op = x % 38;
        if(op != 0){
            if(op <= 10) res += (char)(op + '0' - 1);
            else if(op <= 36) res += (char)(op + 'A' - 11);
            else res += '_';
        }
        x /= 38;
    }
    reverse(res.begin(), res.end());
    ll hash = num(res);        // 再算一次 38 进制整数
    mp12[ha(hash, 12)] = res;  // 填两张哈希表
    mp25[ha(hash, 25)] = res;
    return res;
}

/* 12 位 01 串 -> 查表返回 */
string solve12(string s){
    ll hash = change(s);
    if(mp12.count(hash)) return '#' + mp12[hash];
    else return "###";
}

int main(){
    cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> dat;
        string s1, s2, s;
        if(dat[0] == '0'){                        // 类型 0
            s1 = solve0(dat.substr(1, 28));       // 前 28 位
            s2 = solve0(dat.substr(29, 28));      // 中 28 位
            ll pos = change(dat.substr(57, 15));  // 后 15 位
            if(pos != 0) cout << s1 << ' ' << s2 << ' ' << pos << endl;
            else         cout << s1 << ' ' << s2 << endl;
        }else{                                    // 类型 1
            s2 = solve12(dat.substr(59, 12));     // 后 12 位
            s1 = solve11(dat.substr(1, 58));      // 前 58 位
            if(dat[71] == '0') swap(s1, s2);      // 最后一位决定顺序
            cout << s1 << ' ' << s2 << endl;
        }
    }
}

无注释版本

#include<bits/stdc++.h>

using namespace std;

typedef unsigned long long ll;

int n;
ll c25=33554432;
string dat;
unordered_map<ll,string>mp12,mp25;

ll qmi(ll base,ll m){
    ll res=1;
    for(;m;m/=2,base*=base)
        if(m&1)res*=base;
    return res;
}

ll change(string s){
    ll res=0,base=1;
    for(int i=s.size()-1;i>=0;i--){
        if(s[i]=='1')res+=base;
        base*=2;
    }
    return res;
}

string sh(ll hash){
    string res;
    ll op;
    for(int i=0;i<3;i++){
        op=hash%26;
        res+=(char)(op+'A');
        hash/=26;
    }
    if(hash>0){
        op=hash%10;
        res+=(char)(op+'0');
        hash/=10;
    }else res+='0';
    if(hash>0){
        op=hash%36;
        if(op<=9)res+=(char)(op+'0');
        else res+=(char)(op+'A'-10);
        hash/=36;
    }else res+='0';
    if(hash>0){
        op=hash;
        if(op!=0)
            if(op<=10)res+=(char)(op+'0'-1);
            else res+=(char)(op-11+'A');
    }
    reverse(res.begin(),res.end());
    return res;
}

ll ha(ll op,ll base){
    return op*47055833459>>(64-base);
}

string sanlie(ll hash,ll base){
    if(base==25){
        if(mp25.count(hash))return "#"+mp25[hash];
        else return "###";
    }else{
        if(mp12.count(hash))return "#"+mp12[hash];
        else return "###";
    }
}

ll num(string s){
    ll res=0,base=qmi(38,10);
    int cnt=0;
    for(int i=0;i<s.size();i++){
        if(isdigit(s[i]))res+=base*(s[i]-'0'+1);
        else if(s[i]=='_')res+=base*37;
        else res+=base*(s[i]-'A'+11);
        base/=38;
    }
    return res;
}

string solve0(string s){
    ll hash=change(s);
    if(hash>=c25){
        hash-=c25;
        string s1=sh(hash);
        hash=num(s1);
        mp12[ha(hash,12)]=s1;
        mp25[ha(hash,25)]=s1;
        return s1;
    }else{
        return sanlie(hash,25);
    }
}

string solve11(string s){
    ll x=change(s),op;
    string res;
    for(int i=0;i<11;i++){
        op=x%38;
        if(op!=0){
            if(op<=10)res+=(char)(op+'0'-1);
            else if(op<=36)res+=(char)(op+'A'-11);
            else res+='_';
        }
        x/=38;
    }
    reverse(res.begin(),res.end());
    ll hash=num(res);
    mp12[ha(hash,12)]=res;
    mp25[ha(hash,25)]=res;
    return res;
}

string solve12(string s){
    ll hash=change(s);
    if(mp12.count(hash))return '#'+mp12[hash];
    else return "###";
}

int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>dat;
        string s1,s2,s;
        if(dat[0]=='0'){
            s1=solve0(dat.substr(1,28));
            s2=solve0(dat.substr(29,28));
            ll pos=change(dat.substr(57,15));
            if(pos!=0)cout<<s1<<' '<<s2<<' '<<pos<<endl;
            else cout<<s1<<' '<<s2<<endl;
        }else{
            s2=solve12(dat.substr(59,12));
            s1=solve11(dat.substr(1,58));
            if(dat[71]=='0')swap(s1,s2);
            cout<<s1<<' '<<s2<<endl;
        }
    }
}

                

 

### CCF-CSP 第35认证第三目分析与解法 关于CCF-CSP第35认证中的第三,目前公开的具体目描述尚未广泛传播于网络资源中[^1]。然而,基于以往CSP认证的特点以及考生反馈的信息,可以推测该类目通常考察的是编程基础能力和逻辑思维能力[^2]。 #### 可能涉及的知识点 此类目往往围绕以下几个方面展开: - **数据结构**:如数组、链表、栈、队列等基本操作。 - **算法设计**:包括但不限于贪心算法、动态规划、二分查找等常见方法。 - **字符串处理**:对于输入输出格式的要求较高,需注意边界条件和特殊情况下的行为。 下面给出一个假设性的例子来说明如何解答类似的竞赛问: 假定本是一个简单的区间覆盖或者路径优化问,则解决方案如下所示: ```cpp #include <bits/stdc++.++.h> using namespace std; int main(){ int n; cin >> n; // 输入n表示有多少个区域或节点 vector<pair<int, int>> intervals(n); for(auto &p : intervals){ cin >> p.first >> p.second; // 每组两个数代表起点终点坐标 } sort(intervals.begin(), intervals.end()); long long res = 0; int last_end = INT_MIN; for(const auto &[start,end]:intervals){ if(start>last_end){ res += end-start; last_end=end; } else{ if(end>=last_end){ res+=end-last_end; last_end=end; } } } cout<<res<<endl;// 输出最终结果即总长度或其他目标函数值 } ``` 上述代码片段展示了当面对连续区间的合并计算时的一种通用策略——通过排序并遍历所有给定区间实现最优求解过程[^3]。 #### 注意事项 在参加正式比赛前务必熟悉评测环境配置参数限制情况(时间复杂度O()要求),同时也要关注细节部分比如是否有负权边存在等问影响到具体实现方式的选择上。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值