【CCF-CSP】第39次CSP认证前三题

文章目录

  • 第39次CSP认证前三题题解
    • [1.蒙特卡洛 (签到)](#1.蒙特卡洛 (签到))
      • 1.1题目链接
      • [1.2 题意](#1.2 题意)
      • [C o d e Code Code](#C o d e Code Code)
    • [2.水印检查 (差分)](#2.水印检查 (差分))
      • [2.1 题目链接:](#2.1 题目链接:)
      • [2.2 题意](#2.2 题意)
      • [2.3 子任务](#2.3 子任务)
      • [2.4 80%数据](#2.4 80%数据)
      • [2.5 100%数据](#2.5 100%数据)
    • [3.HTTP 头信息 (大模拟,未测)](#3.HTTP 头信息 (大模拟,未测))
      • 3.1题目链接
      • [3.2 题意](#3.2 题意)
      • [3.3 子任务](#3.3 子任务)
      • [3.4 对于40%数据](#3.4 对于40%数据)
      • [3.5 对于100%数据 代码解析](#3.5 对于100%数据 代码解析)
        • [3.5.1 预定义变量](#3.5.1 预定义变量)
        • [3.5.2 重建Huffman树,销毁树](#3.5.2 重建Huffman树,销毁树)
        • [3.5.3 将十六进制数转二进制后序列解码为对应字符串](#3.5.3 将十六进制数转二进制后序列解码为对应字符串)
        • [3.5.4 十六进制转二进制](#3.5.4 十六进制转二进制)
        • [3.5.5 根据二进制序列解码huffmanCode](#3.5.5 根据二进制序列解码huffmanCode)
        • [3.5.6 处理字段名和字段值](#3.5.6 处理字段名和字段值)
        • [3.5.7 插入动态表数据](#3.5.7 插入动态表数据)
        • [3.5.8 根据索引获取表数据](#3.5.8 根据索引获取表数据)
        • [3.5.9 完整代码](#3.5.9 完整代码)
        • [3.5.10 时间复杂度分析](#3.5.10 时间复杂度分析)

第39次CSP认证前三题题解

1.蒙特卡洛 (签到)

1.1题目链接

蒙特卡洛

1.2 题意

检测在圆内点的数量,按给定公式输出即可

C o d e Code Code

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;

int main() {
    int n, a;
    cin >> n >> a;
    int res = 0;
    for (int i = 0; i < n; i++) {
        double x, y;
        cin >> x >> y;
        if (x * x + y * y <= a * a)
            res ++;
    }
    printf("%.6f\n", 4.0 * res / n);
    return 0;
}

2.水印检查 (差分)

2.1 题目链接:

水印检查

2.2 题意

预先给你一个 5 ∗ 9 5*9 5∗9的 01 01 01矩阵,并给你一个 n ∗ n n*n n∗n的矩阵 A A A,以及 L L L表示灰度值范围,对于每个不同的灰度值要求我们找到能检测出与初始 5 ∗ 9 5*9 5∗9矩阵一样的子矩阵,这个灰度值即合法。从小到大输出所有合法的灰度值。

2.3 子任务

  • 80% 的测试点满足: n ≤ 50 , L = 256 ; n ≤ 50 , L = 256 ; n ≤ 50 , L = 256 ; n ≤ 50 , L = 256 ; n≤50,L=256;n≤50,L=256; n≤50,L=256;n≤50,L=256;n≤50,L=256;
  • 全部的测试点满足: 9 ≤ n ≤ 200 、 L ∈ 256 , 65536 9 ≤ n ≤ 200 、 L ∈ { 256 , 65536 } , 9 ≤ n ≤ 200 、 L ∈ 256 , 65536 9 ≤ n ≤ 200 、 L ∈ { 256 , 65536 } 9≤n≤200、L∈\{256,65536\},9≤n≤200、L∈{256,65536} 9≤n≤200、L∈256,655369≤n≤200、L∈{256,65536},9≤n≤200、L∈256,65536,像素值均在 [ 0 , L − 1 ] [ 0 , L − 1 ] [ 0 , L − 1 ] [ 0 , L − 1 ] [0,L−1][0,L−1] [0,L−1][0,L−1][0,L−1]范围内,且保证至少存在一个阈值可以检测出水印 CSP。

2.4 80%数据

对于80%的测试点我们可以直接枚举即可

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;

const int N = 2e2 + 7;

int n, L;
int A[N][N];
bool st[N][N];

void init() {
    st[1][1] = st[1][2] = st[1][4] = st[1][5] = st[1][7] = true;
    st[2][1] = st[2][2] = st[2][8] = true;
    st[3][1] = st[3][2] = st[3][3] = st[3][4] = st[3][7] = st[3][8] = true;
    st[4][7] = st[4][8] = true;
}

bool check(int x, int y, int k) {
    for (int i = x, a = 0; i < x + 5; i++, a++)
        for (int j = y, b = 0; j < y + 9; j++, b++)
            if ((A[i][j] < k) ^ st[a][b])
                return false;
    return true;
}

int main() {
    init();
    cin >> n >> L;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            cin >> A[i][j];

    for (int k = 0; k < L; k++) {
        bool flag = false;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if (check(i, j, k)) {
                    flag = true;
                    break;
                }
            }
            if (flag) break;
        }
        if (flag) cout << k << endl;
    }
    return 0;
}

2.5 100%数据

对于每个 5 ∗ 9 5*9 5∗9的子矩阵我们不枚举 k k k,而是直接计算出满足条件的 k k k的取值范围 [ m i n k , m a x k ] [min_k, max_k] [mink,maxk],然后差分处理这个区间最后再前缀和还原输出即可。
C o d e Code Code

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;

const int N = 2e2 + 7, M = 6e6 + 7;

int n, L;
int A[N][N], diff[M];
bool st[5][9];

void init() {
    st[1][1] = st[1][2] = st[1][4] = st[1][5] = st[1][7] = true;
    st[2][1] = st[2][2] = st[2][8] = true;
    st[3][1] = st[3][2] = st[3][3] = st[3][4] = st[3][7] = st[3][8] = true;
    st[4][7] = st[4][8] = true;
}

int main() {
    init();
    cin >> n >> L;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            cin >> A[i][j];

    for (int i = 1; i <= n - 4; i++) {
        for (int j = 1; j <= n - 8; j++) {
            // 计算当前窗口的最小k值和最大k值
            int min_k = 0, max_k = L - 1;
            bool flag = true;
            
            // 计算最小k值(来自模板中为true的位置)
            for (int a = 0; a < 5; a++) 
                for (int b = 0; b < 9; b++) 
                    if (st[a][b]) 
                        min_k = max(min_k, A[i + a][j + b] + 1);
            
            // 计算最大k值(来自模板中为false的位置)
            for (int a = 0; a < 5 && flag; a++) {
                for (int b = 0; b < 9 && flag; b++) {
                    if (!st[a][b]) 
                    {
                        if (A[i + a][j + b] < min_k) 
                            flag = false;
                        else 
                            max_k = min(max_k, A[i + a][j + b]);
                    }
                }
            }
            
            if (flag && min_k <= max_k) 
                diff[min_k] ++, diff[max_k + 1] --; // 差分处理:[min_k, max_k]区间
        }
    }
    
    int cnt = 0;
    for (int k = 0; k < L; k++) {
        cnt += diff[k]; // 前缀和还原
        if (cnt > 0) 
            cout << k << endl;
    }
    return 0;
}

3.HTTP 头信息 (大模拟,未测)

3.1题目链接

HTTP 头信息

3.2 题意

  • 给你两个表格:静态表格( S S S个条目)与动态表格(最多 D D D个条目)其中静态表格是输入时已经给你的;动态表格是后面要操作要插入的,如果满,要去掉最后面的,并将新的插入到最前面。

  • 给你有三种操作:

    • 表格引用型指定:直接按编号输出静态表格中某条目的字段名与字段值
    • 字面量并索引指定:指令包括一个编号,如果编号为 0,则另外跟随一个字符串,表示字段名;否则表示使用静态或动态表格中对应编号的条目的字段名。该指令还包括一个字符串,表示字段值。输出该字段名和字段值后,再将该键值对插入到动态表格
    • 字面量不索引指令:同操作2,但不插入到动态表格中。
  • 字符串:上述指定中给定的字符串分为普通字符串和Huffman编码的字符串

    • 普通字符串s,当 s 的开头不是 H 时,表示原样输出字符串 s;当 s 的开头是 HH 时,表示输出字符串 s 去掉开头的第一个 H 后的部分;
    • Huffman 编码的字符串Hb,其中 b 是一个由偶数个 0-9a-f 组成的字符串,表示原始字符串经过 Huffman 编码后得到的二进制数据序列(以十六进制显示)。其中,原始字符串靠前的字符对应的编码,存储于每一字节较高的位;每个字符的编码的高位存储于每一字节较高的位;每个字符的编码按位连续存储;最后一字节的低位补 0,并增加一字节表示补 0 的个数 p ( 0 ≤ p ≤ 7 ) p(0≤p≤7) p(0≤p≤7)。例如,按前述 Huffman 树对字符串 abcd 进行编码,得到的二进制数据序列为 100010011,补 0 后为 10001001 10000000,最后一字节补了 7 个 0,因此表示为 H898007
  • Huffman编码树:如001b01c1d1a 所表示的即为如下树,字符 a 的编码为 1;字符 b 的编码为 00;字符 c 的编码为 010;字符 d 的编码为 011。例如对于样例中给定的H898007 就使用了Huffman编码,最后的07 表示最后一个字节补了 7 个 0,因此十六进制串 8980 对应的二进制数据序列为 100010011,遍历该二进制序列按照左0右1走huffman树遇到叶节点则存下来继续回到根节点按左0右1走 ,以此解码后得到的字符串即为 abcd

    复制代码
            *
           / \
          *   a
         / \
        b   *
           / \
          c   d
  • 输入:输入的第一行包含两个整数 S S S 和 D D D,分别表示静态表格和动态表格的条目数。接下来 S S S 行,每行包含空格分隔的两个字符串,依次表示静态表格中每个条目的字段名和字段值。接下来的一行包含一个字符串,表示 Huffman 树的表示。接下来的一行包含一个整数 N N N,表示指令的条数。接下来 N N N 行,每行表示一条指令,格式如前述所述。

  • 输出:输出 N N N行,每行表示解码后的头信息,格式为 key: value,其中 keyvalue分别表示字段名和字段值。对于输入中的"普通字符串"和"使用霍夫曼编码的字符串",需输出解码后得到的原始字符串

3.3 子任务

  • 对于 20% 的数据,仅包含表格引用指令;
  • 对于 40% 的数据,仅包含表格引用指令和字面量不索引指令,且不包含 Huffman编码的字符串
  • 对于 60% 的数据,含有所有类型的指令,且字面量并索引指令的数目不超过 D;
  • 对于 100% 的数据,有: 1 ≤ S ≤ 64 , 1 ≤ D ≤ 120 1≤S≤64,1≤D≤120 1≤S≤64,1≤D≤120;Huffman 树中字符的 01 编码长度不超过 8 位;
    1 ≤ N ≤ 1000 1≤N≤1000 1≤N≤1000;表示字段名和字段值的字符串在解码前后的长度均不超过 150。

3.4 对于40%数据

我们可以完全不处理huffman编码的字符串,且不用插入处理动态表格,代码如下

cpp 复制代码
#include <bits/stdc++.h>

using namespace std;
using PSS = pair<string, string>;

int N, S, D;
string huffmanCode;
vector<PSS> STable;

string work(const string& s) {
    if (s.size() >= 2 && s[0] == 'H' && s[1] == 'H') {
        return s.substr(1);
    }
    // 其他情况直接返回(包括普通字符串和单H开头的字符串)
    return s;
}

int main() {
    cin >> S >> D;
    for (int i = 0; i < S; i++) {
        string k, v;
        cin >> k >> v;
        STable.emplace_back(work(k), work(v));
    }
    cin >> huffmanCode;;
    cin >> N;
    while (N --) {
        int op;
        cin >> op;
        if (op == 1) {  // 表格引用指令
            int idx;
            cin >> idx;
            cout << STable[idx - 1].first << ": " << STable[idx - 1].second << endl;
        }
        else if (op == 3) {  // 字面量不索引指令
            int idx;
            cin >> idx;
            if (idx == 0) {
                string k, v;
                cin >> k >> v;
                cout << work(k) << ": " << work(v) << endl;
            } else {
                string v;
                cin >> v;
                cout << STable[idx - 1].first << ": " << work(v) << endl;
            }
        }
    }
    
    return 0;
}

3.5 对于100%数据 代码解析

3.5.1 预定义变量
cpp 复制代码
#define x first
#define y second
using namespace std;
using PSS = pair<string, string>;

int N, S, D;
string huffmanCode; // huffman编码树的表示
vector<PSS> STable, DTable; // 静态表 动态表
3.5.2 重建Huffman树,销毁树
cpp 复制代码
struct Node {
    char data;
    Node* left, *right;
    Node(char d) : data(d), left(nullptr), right(nullptr) {}
    Node() : data('\0'), left(nullptr), right(nullptr) {}
};

Node* rebuild(const string& s, int& index) {
    if (index >= s.length()) return nullptr;
    if (s[index] == '1') {
        index++; // 跳过'1'
        char ch = s[index++]; // 读取字符
        return new Node(ch);
    } else if (s[index] == '0') {
        index++; // 跳过'0'
        auto node = new Node();
        node->left = rebuild(s, index);
        node->right = rebuild(s, index);
        return node;
    }
    return nullptr;
}

// 销毁Huffman树
void destroyTree(Node* root) {
    if (!root) return;
    destroyTree(root->left), destroyTree(root->right);
    delete root;
}
3.5.3 将十六进制数转二进制后序列解码为对应字符串
cpp 复制代码
string huffmanDecode(Node* root, const string& binaryStr) {
    string res;
    Node* cur= root;
    for (auto &bit : binaryStr) {
        cur = (bit == '0' ? cur->left : cur->right);
        if (!cur->left && !cur->right) {
            res += cur->data;
            cur = root;
        }
    }
    return res;
}
3.5.4 十六进制转二进制
cpp 复制代码
string hexToBinary(const string& hexStr) {
    // 映射表
    static const string hexMap[] = {
            "0000", "0001", "0010", "0011", // 0-3
            "0100", "0101", "0110", "0111", // 4-7
            "1000", "1001", "1010", "1011", // 8-b
            "1100", "1101", "1110", "1111"  // c-f
    };
    string res;
    for (auto &c : hexStr) {
        res += isdigit(c) ? hexMap[c - '0'] : hexMap[c - 'a' + 10];
    }
    return res;
}
3.5.5 根据二进制序列解码huffmanCode
cpp 复制代码
string huffmanDecode(Node* root, const string& binaryStr) {
    string res;
    Node* cur= root;
    for (auto &bit : binaryStr) {
        cur = (bit == '0' ? cur->left : cur->right);
        if (!cur->left && !cur->right) {
            res += cur->data;
            cur = root;
        }
    }
    return res;
}
3.5.6 处理字段名和字段值
cpp 复制代码
string work(string s, Node* huffmanTree) {
    // 普通字符串
    if (s[0] != 'H') return s;
    else if (s.size() >= 2 && s[0] == 'H' && s[1] == 'H') return s.substr(1); // 双H开头的情况
    else { // Huffman编码的字符串
        // 提取补0个数(最后2个字符)
        if (s.length() < 3) return s;
        
        int cnt = stoi(s.substr(s.size() - 2)); // 补0数
        string hexData = s.substr(1, s.length() - 3); // 提取十六进制数据
        string binaryStr = hexToBinary(hexData); // 十六进制转二进制
        // 去掉补的0
        if (cnt > 0 && binaryStr.size() >= cnt) 
            binaryStr = binaryStr.substr(0, binaryStr.size() - cnt);
        return huffmanDecode(huffmanTree, binaryStr); // Huffman解码
    }
}
3.5.7 插入动态表数据
cpp 复制代码
void insertDynamicTable(const string &k, const string &v) {
    DTable.insert(DTable.begin(), {k, v});
    if (DTable.size() > D) {
        DTable.pop_back();
    }
}
3.5.8 根据索引获取表数据
cpp 复制代码
PSS getEntry(int index) {
    if (index <= S) { // 静态表格条目(1-based索引)
        return STable[index - 1];
    } else { // 动态表格条目(1-based索引,需要转换)
        int idx = index - S - 1;
        if (idx >= 0 && idx < DTable.size()) {
            return DTable[idx];
        } else {
            return {"", ""}; // 无效索引
        }
    }
}
3.5.9 完整代码

C o d e Code Code

cpp 复制代码
#include <bits/stdc++.h>

#define x first
#define y second
using namespace std;
using PSS = pair<string, string>;

int N, S, D;
string huffmanCode;
vector<PSS> STable, DTable;

struct Node {
    char data;
    Node* left, *right;
    Node(char d) : data(d), left(nullptr), right(nullptr) {}
    Node() : data('\0'), left(nullptr), right(nullptr) {}
};

Node* rebuild(const string& s, int& index) {
    if (index >= s.length()) return nullptr;
    if (s[index] == '1') {
        index++; // 跳过'1'
        char ch = s[index++]; // 读取字符
        return new Node(ch);
    } else if (s[index] == '0') {
        index++; // 跳过'0'
        auto node = new Node();
        node->left = rebuild(s, index);
        node->right = rebuild(s, index);
        return node;
    }
    return nullptr;
}

void destroyTree(Node* root) {
    if (!root) return;
    destroyTree(root->left), destroyTree(root->right);
    delete root;
}

string hexToBinary(const string& hexStr) {
    // 映射表
    static const string hexMap[] = {
            "0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", // 0-7
            "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111"  // 8-f
    };
    string res;
    for (auto &c : hexStr) {
        res += isdigit(c) ? hexMap[c - '0'] : hexMap[c - 'a' + 10];
    }
    return res;
}

string huffmanDecode(Node* root, const string& binaryStr) {
    string res;
    Node* cur= root;
    for (auto &bit : binaryStr) {
        cur = (bit == '0' ? cur->left : cur->right);
        if (!cur->left && !cur->right) {
            res += cur->data;
            cur = root;
        }
    }
    return res;
}

string work(string s, Node* huffmanTree) {
    // 普通字符串
    if (s[0] != 'H') return s;
    else if (s.size() >= 2 && s[0] == 'H' && s[1] == 'H') return s.substr(1); // 双H开头的情况
    else { // Huffman编码的字符串
        // 提取补0个数(最后2个字符)
        if (s.size() < 3) return s;

        int cnt = stoi(s.substr(s.size() - 2)); // 补0数
        string hexData = s.substr(1, s.size() - 3); // 提取十六进制数据
        string binaryStr = hexToBinary(hexData); // 十六进制转二进制
        // 去掉补的0
        if (cnt > 0 && binaryStr.size() >= cnt)
            binaryStr = binaryStr.substr(0, binaryStr.size() - cnt);
        return huffmanDecode(huffmanTree, binaryStr); // Huffman解码
    }
}

void insertDTable(const string &k, const string &v) {
    DTable.insert(DTable.begin(), {k, v});
    if (DTable.size() > D) {
        DTable.pop_back();
    }
}

PSS getEntry(int index) {
    if (index <= S) {
        return STable[index - 1];
    } else {
        int idx = index - S - 1;
        if (idx >= 0 && idx < DTable.size()) {
            return DTable[idx];
        }
    }
    return {"", ""};
}

int main() {
    cin.tie(nullptr)->ios::sync_with_stdio(false);
    cin >> S >> D;
    for (int i = 0; i < S; i++) {
        string k, v;
        cin >> k >> v;
        STable.emplace_back(k, v);
    }
    cin >> huffmanCode;
    int index = 0;
    Node* huffmanTree = rebuild(huffmanCode, index); // 建树

    for (auto &[k, v] : STable) { // 预处理静态表字符串 避免多次cwork出错
        k = work(k, huffmanTree), v = work(v, huffmanTree);
    }

    cin >> N;
    for (int i = 0; i < N; i++) {
        int op;
        string k, v;
        cin >> op;
        if (op == 1) { // 表格引用指令
            int idx;
            cin >> idx;
            auto entry = getEntry(idx);
            cout << entry.x << ": " << entry.y << '\n';
        }
        else if (op == 2) { // 字面量并索引指令
            int idx;
            cin >> idx;
            if (idx == 0) {
                cin >> k >> v;
                string key = work(k, huffmanTree), value = work(v, huffmanTree);
                cout << key << ": " << value << '\n';
                insertDTable(key, value);
            } else {
                cin >> v;
                auto entry = getEntry(idx);
                // 注意已经插入过的key 已经work过不需要再work 重复work可能会出错
                string key = entry.x, value = work(v, huffmanTree);
                cout << key << ": " << value << '\n';
                insertDTable(key, value);
            }
        }
        else if (op == 3) { // 字面量不索引指令
            int idx;
            cin >> idx;
            if (idx == 0) {
                cin >> k >> v;
                cout << work(k, huffmanTree) << ": " << work(v, huffmanTree) << '\n';
            } else {
                cin >> v;
                auto entry = getEntry(idx);
                // 注意已经插入过的key 已经work过不需要再work
                cout << entry.x << ": " << work(v, huffmanTree)<< '\n';
            }
        }
    }
    destroyTree(huffmanTree);
    return 0;
}
3.5.10 时间复杂度分析
  • S: 静态表大小,L: Huffman编码长度,W: work函数平均复杂度,N: 指令数量,D: 动态表最大容量
  • Huffman树重建: O ( L ) O(L) O(L)
  • 静态表预处理: O ( S ∗ W ) O(S*W) O(S∗W)
  • 处理N条指令: O ( N ∗ ( W + D ) ) O(N * (W+D)) O(N∗(W+D))
  • work函数: O ( L ) O(L) O(L)
  • 总体: O ( S + L + S × W + N × ( W + D ) ) O(S + L + S×W + N×(W+D)) O(S+L+S×W+N×(W+D))
  • 总体时间不会超时,也可以vector换成deque处理,能优化些许时间。
相关推荐
墨染点香4 小时前
LeetCode 刷题【90. 子集 II】
算法·leetcode·职场和发展
Code_LT6 小时前
【算法】多榜单排序->综合排序问题
人工智能·算法
仟濹6 小时前
【力扣LeetCode】 1413_逐步求和得到正数的最小值
算法·leetcode·职场和发展
9毫米的幻想6 小时前
【Linux系统】—— 进程切换&&进程优先级&&进程调度
linux·运维·服务器·c++·学习·算法
西阳未落9 小时前
数据结构初阶——哈希表的实现(C++)
算法·哈希算法·散列表
未知陨落9 小时前
LeetCode:46.二叉树展开为链表
算法·leetcode·链表
小欣加油9 小时前
leetcode 206 反转链表
数据结构·c++·算法·leetcode·链表·职场和发展
野犬寒鸦9 小时前
力扣hot100:环形链表II(哈希算法与快慢指针法思路讲解)
java·数据结构·算法·leetcode·链表·哈希算法
强化学习与机器人控制仿真10 小时前
LeRobot 入门教程(九)使用 Android、iOS 手机控制机械臂
开发语言·人工智能·stm32·深度学习·神经网络·算法·机器人