文章目录
- 第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题目链接
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
,其中key
和value
分别表示字段名和字段值。对于输入中的"普通字符串"和"使用霍夫曼编码的字符串",需输出解码后得到的原始字符串。
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
处理,能优化些许时间。