csp39 3,4,5 题
HTTP 头信息
时间限制: 1.0 秒
空间限制: 512 MiB
相关文件: 题目目录
题目背景
西西艾弗岛大数据公司的服务器每天要处理海量的 HTTP 请求。为了提高传输效率,公司决定将所有服务器的 HTTP/1.1 版本升级到 HTTP/2 版本。小 C 作为一名实习生被分配到这个项目中,负责实现 HTTP 头信息的压缩算法 HPACK。HTTP(超文本传输协议)是万维网的基础协议。HTTP/1.1 版本中, 头信息使用纯文本格式传输,每个头字段占一行,字段名和字段值之间用冒号加空格分隔。HTTP/2 版本中, 头信息使用 HPACK(二进制格式)传输,减少了传输的数据量。
HTTP 的头信息,是一系列有序的键值对(key-value pairs),用于描述请求或响应的元数据,其中, 键是字段名,值是字段值,并且字段名是可以重复的。HPACK 引入了两张表格:静态表格和动态表格, 这两张表格中都存储有一系列的字段名和字段值。静态表格是预定义的,包含了一些常用的键值对; 而动态表格则是根据实际的请求和响应动态更新的。在解码开始前,动态表格为空。在解码过程中, 会不断向动态表格中添加新的键值对。动态表格的大小是有限的,当动态表格的条目数超过限制时, 会删除最旧的条目以腾出空间。静态表格和动态表格的条目可以出现重复,即可能存在字段名相同但字段值不同的条目; 也可能存在字段名和字段值都相同的条目。静态表格和动态表格的条目从 1 开始编号。假设静态表格有 SS 条目, 动态表格有 DD 条目,则静态表格中的条目依次编号为 1,2,…,S1,2,…,S,动态表格中的条目依次编号为 S+1,S+2,…,S+DS+1,S+2,…,S+D。当向动态表格中添加新的条目时,将新的条目插入到动态表格的最前面,即编号为 S+1S+1 的位置, 并将原有的条目依次向后移动一个位置。当动态表格中的条目数超过限制时,则去掉编号最大的条目。
HPACK 编码后的头信息由一系列的指令组成,每条指令表示一个头信息,将这些指令按顺序解码即可得到原始的头信息。 指令分为如下几种类型:
- 表格引用指令:即直接按照编号输出静态或动态表格中的某个条目的字段名和字段值;
- 字面量并索引指令:该指令包括一个编号,如果编号为 0,则另外跟随一个字符串,表示字段名;否则表示使用静态或动态表格中对应编号的条目的字段名。该指令还包括一个字符串,表示字段值。输出该字段名和字段值后,再将该键值对插入到动态表格;
- 字面量不索引指令:该指令与字面量并索引指令类似,但不会将该键值对插入到动态表格中。
在前述指令中,出现的作为字段名或字段值的字符串,可以是原样输出的字符串,也可以是经过 Huffman 编码的字符串。
Huffman 编码是一种常用的无损数据压缩算法。它通过构造一棵二叉树来为每个字符分配一个唯一的二进制编码, 并使得常用字符的编码更短,从而减少整体数据的长度。在二叉树中,每个叶子节点表示一个字符,从根节点到叶子节点的路径表示该字符的编码:在路径上,向左走表示添加一个 0,向右走表示添加一个 1。这样,常用字符会出现在树的上层,拥有较短的编码,而不常用字符则会出现在树的下层,拥有较长的编码。
题目描述
为了表示一个 Huffman 编码树,对于 Huffman 树从根节点开始的每一个节点,如果这个节点是叶子节点, 那么输出 1,并且输出该节点对应的字符;否则输出 0,然后递归地对该节点的左子节点和右子节点进行同样的操作。 例如:
*/ \* a/ \b */ \c d
可以表示为 001b01c1d1a。根据该表示,即可还原出 Huffman 树。上面的树中,字符 a 的编码为 1;字符 b 的编码为 00;字符 c 的编码为 010;字符 d 的编码为 011。
对于前述三种指令,其格式约定如下:
- 表格引用指令:
1 i,其中i是一个正整数,表示引用编号为 ii 的条目; - 字面量并索引指令:
2 0 k v或2 i v,其中i是一个正整数,表示使用编号为 ii 的条目的字段名;k和v是字符串或 Huffman 编码的字符串,分别表示字段名和字段值; - 字面量不索引指令:
3 0 k v或3 i v,其中i是一个正整数,表示使用编号为 ii 的条目的字段名;k和v是字符串或 Huffman 编码的字符串,分别表示字段名和字段值。
其中,字符串是由小写字母、大写字母、数字(a-zA-Z0-9)组成的,其格式约定如下:
- 普通字符串:
s,当s的开头不是H时,表示原样输出字符串s;当s的开头是HH时,表示输出字符串s去掉开头的第一个H后的部分; - Huffman 编码的字符串:
Hb,其中b是一个由偶数个0-9a-f组成的字符串,表示原始字符串经过 Huffman 编码后得到的二进制数据序列(以十六进制显示)。其中,原始字符串靠前的字符对应的编码,存储于每一字节较高的位;每个字符的编码的高位存储于每一字节较高的位;每个字符的编码按位连续存储;最后一字节的低位补 0,并增加一字节表示补 0 的个数 pp(0≤p≤70≤p≤7)。例如,按前述 Huffman 树对字符串abcd进行编码,得到的二进制数据序列为100010011,补 0 后为10001001 10000000,最后一字节补了 7 个 0,因此表示为H898007。
输入格式
从标准输入读入数据。
输入的第一行包含两个整数 SS 和 DD,分别表示静态表格和动态表格的条目数。接下来 SS 行,每行包含空格分隔的两个字符串,依次表示静态表格中每个条目的字段名和字段值。
接下来的一行包含一个字符串,表示 Huffman 树的表示。接下来的一行包含一个整数 NN,表示指令的条数。接下来 NN 行,每行表示一条指令,格式如前述所述。
输出格式
输出到标准输出。
输出 NN 行,每行表示解码后的头信息,格式为 key: value,其中 key 和 value 分别表示字段名和字段值。对于输入中的“普通字符串”和“使用霍夫曼编码的字符串”,需输出解码后得到的原始字符串。
样例1输入
3 3
Foo Bar
Foo Barz
bar barzz1
001b01c1d1a
11
1 1
3 3 ABCD
3 0 H898007 HHIJK
2 0 H898007 EFGH
1 4
2 0 DDDD EEEE
2 5 GGGG
1 4
1 6
2 0 HHHH IIII
1 4
样例1输出
Foo: Bar
bar: ABCD
abcd: HIJK
abcd: EFGH
abcd: EFGH
DDDD: EEEE
abcd: GGGG
abcd: GGGG
abcd: EFGH
HHH: IIII
HHH: IIII
样例1解释
输入首先给出了静态表的内容,和动态表的大小限制 D=3D=3。接着给出了 Huffman 树的表示 001b01c1d1a, 该表示即题目描述中的示例。接下来,输入给出了 11 个指令。其中,第一个指令表示引用静态表中编号为 1 的条目,因此直接输出 Foo: Bar。第二个指令表示使用静态表中第 3 项的字段名作为字段名,字段值为给定的 ABCD, 因此输出 bar: ABCD。第三个指令中,字段名使用了 Huffman 编码。其中,最后的 07 表示最后一个字节补了 7 个 0,因此十六进制串 8980 对应的二进制数据序列为 100010011, 解码后得到字符串 abcd。第三个指令中,字段值前有两个 H,需要去掉一个,因此得到输出为 abcd: HIJK。 第四个指令与第三个指令类似,但是要将得到的键值对插入到动态表中。第五个指令引用了编号为 4 的项目, 即动态表的第一项,因此输出 abcd: EFGH。第六个指令继续向动态表中插入新的键值对,此时动态表中已有两项。 第七个指令中,字段名使用了编号为 5 的条目,即动态表的第二项。需要注意的是,每次向动态表中插入新的条目时, 都将新的条目插入到动态表的最前面,并将原有的条目依次向后移动一个位置。因此,此时的动态表的第二项, 即为第四个指令中插入的条目,所以输出为 abcd: GGGG,然后再将该条目插入到动态表中。 第八个指令引用了编号为 4 的条目,即动态表的第一项,因此输出 abcd: GGGG。 第九个指令引用了编号为 6 的条目,即动态表的第三项,因此输出 abcd: EFGH。 第十个指令向动态表中继续插入一个条目,插入前动态表已满,因此需要在插入新条目前, 先丢弃最后一个条目,再将新条目插入到动态表的最前面。第十一个指令引用了编号为 4 的条目, 即动态表的第一项,因此输出 HHH: IIII。
子任务
对于 20% 的数据,仅包含表格引用指令;
对于 40% 的数据,仅包含表格引用指令和字面量不索引指令,且不包含 Huffman 编码的字符串;
对于 60% 的数据,含有所有类型的指令,且字面量并索引指令的数目不超过 DD;
对于 100% 的数据,有:1≤S≤641≤S≤64;1≤D≤1201≤D≤120;Huffman 树中字符的 01 编码长度不超过 8 位;1≤N≤10001≤N≤1000;表示字段名和字段值的字符串在解码前后的长度均不超过 150。
提示
本题提供了一些大语言模型生成的代码模块辅助解题(见下发文件),你可以参考、使用这些代码,也可以忽略它们自己手动实现全部功能,详情如下。
小 C 打开了某个大语言模型,并向它提问,以下是提问和回答:
提问:为了表示一个 Huffman 编码树,对于 Huffman 树从根节点开始的每一个节点,如果这个节点是叶子节点, 那么输出 1,并且输出该节点对应的字符;否则输出 0,然后递归地对该节点的左子节点和右子节点进行同样的操作。请根据上述表示的办法,将 Huffman 编码树恢复为树状结构。请分别给出 C++、Python、Java 语言的实现。
回答:我将为您提供C++、Python和Java三种语言的Huffman树恢复实现。
C++ 实现
#include <iostream> #include <string> #include <memory> using namespace std;struct Node {char data;shared_ptr<Node> left;shared_ptr<Node> right;Node(char d) : data(d), left(nullptr), right(nullptr) {}Node() : data('\0'), left(nullptr), right(nullptr) {} };shared_ptr<Node> rebuildHuffmanTree(const string& s, int& index) {if (index >= s.length()) return nullptr;if (s[index] == '1') {index++; // 跳过'1'char ch = s[index++]; // 读取字符return make_shared<Node>(ch);} else if (s[index] == '0') {index++; // 跳过'0'auto node = make_shared<Node>();node->left = rebuildHuffmanTree(s, index);node->right = rebuildHuffmanTree(s, index);return node;}return nullptr; }// 辅助函数:打印树结构(前序遍历) void printTree(const shared_ptr<Node>& root, string prefix = "") {if (!root) return;if (root->data != '\0') {cout << prefix << "Leaf: " << root->data << endl;} else {cout << prefix << "Internal Node" << endl;}printTree(root->left, prefix + " ");printTree(root->right, prefix + " "); }int main() {string encodedTree = "001a1b1c"; // 示例:0表示内部节点,1表示叶子节点int index = 0;auto root = rebuildHuffmanTree(encodedTree, index);cout << "Rebuilt Huffman Tree:" << endl;printTree(root);return 0; }Python 实现
class Node:def __init__(self, data=None):self.data = dataself.left = Noneself.right = Nonedef rebuild_huffman_tree(s, index):if index[0] >= len(s):return Noneif s[index[0]] == '1':index[0] += 1 # 跳过'1'ch = s[index[0]]index[0] += 1 # 读取字符return Node(ch)elif s[index[0]] == '0':index[0] += 1 # 跳过'0'node = Node()node.left = rebuild_huffman_tree(s, index)node.right = rebuild_huffman_tree(s, index)return nodereturn None# 辅助函数:打印树结构 def print_tree(root, prefix=""):if root is None:returnif root.data is not None:print(f"{prefix}Leaf: {root.data}")else:print(f"{prefix}Internal Node")print_tree(root.left, prefix + " ")print_tree(root.right, prefix + " ")# 使用示例 if __name__ == "__main__":encoded_tree = "001a1b1c" # 示例编码index = [0] # 使用列表来传递可变索引root = rebuild_huffman_tree(encoded_tree, index)print("Rebuilt Huffman Tree:")print_tree(root)Java 实现
class Node {Character data;Node left;Node right;Node(Character data) {this.data = data;this.left = null;this.right = null;}Node() {this.data = null;this.left = null;this.right = null;} }public class HuffmanTreeRebuilder {public static Node rebuildHuffmanTree(String s, int[] index) {if (index[0] >= s.length()) {return null;}if (s.charAt(index[0]) == '1') {index[0]++; // 跳过'1'char ch = s.charAt(index[0]);index[0]++; // 读取字符return new Node(ch);} else if (s.charAt(index[0]) == '0') {index[0]++; // 跳过'0'Node node = new Node();node.left = rebuildHuffmanTree(s, index);node.right = rebuildHuffmanTree(s, index);return node;}return null;}// 辅助函数:打印树结构public static void printTree(Node root, String prefix) {if (root == null) {return;}if (root.data != null) {System.out.println(prefix + "Leaf: " + root.data);} else {System.out.println(prefix + "Internal Node");}printTree(root.left, prefix + " ");printTree(root.right, prefix + " ");}public static void main(String[] args) {String encodedTree = "001a1b1c"; // 示例编码int[] index = {0}; // 使用数组来传递可变索引Node root = rebuildHuffmanTree(encodedTree, index);System.out.println("Rebuilt Huffman Tree:");printTree(root, "");} }
40分代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int s, d, n;
typedef pair<string, string> pii;
vector<pii> vet;signed main()
{cin >> s >> d;vet.push_back({"",""});for(int i = 1; i <= s; i++){string a, b;cin >> a >> b;vet.push_back({a, b});}string ss;cin >> ss;cin >> n;for(int i = 1; i <= n; i++){int x;int y;cin >> x >> y;if(x == 1){cout << vet[y].first << ": " << vet[y].second << endl;}if(x == 3 ){if(y != 0){string str;cin >> str;string t = str.substr(0, 2);if(t == "HH")str.erase(0,1);cout << vet[y].first << ": " << str << endl;}if(y == 0){string s1, s2;cin >> s1 >> s2;string t1 = s1.substr(0, 2), t2 = s2.substr(0, 2);if(t1 == "HH") s1.erase(0, 1);if(t2 == "HH") s2.erase(0, 1);cout << s1 << ": " << s2 << endl;}}}
}
100分代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int s, d, n;
typedef pair<string, string> pii;
vector<pii> vet;
vector<pii> v2;
struct Node {char data;shared_ptr<Node> left;shared_ptr<Node> right;Node(char d) : data(d), left(nullptr), right(nullptr) {}Node() : data('\0'), left(nullptr), right(nullptr) {}
};
shared_ptr<Node> root;
deque<pii> deq;
shared_ptr<Node> rebuildHuffmanTree(const string& s, int& index) {if (index >= s.length()) return nullptr;if (s[index] == '1') {index++; // 跳过'1'char ch = s[index++]; // 读取字符return make_shared<Node>(ch);} else if (s[index] == '0') {index++; // 跳过'0'auto node = make_shared<Node>();node->left = rebuildHuffmanTree(s, index);node->right = rebuildHuffmanTree(s, index);return node;}return nullptr;
}
string f1(string s)
{// 映射表string last2 = s.substr(s.size()- 2, 2);int qu0geshu = stoi(last2,nullptr, 16);s.erase(s.size() -2, 2);string a[] = {"0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111" };string res;for (auto &c : s) {res += isdigit(c) ? a[c - '0'] : a[c - 'a' + 10];}res.erase(res.size() - qu0geshu, qu0geshu);string ans;auto cur = root;for(auto &bit : res){if(bit == '0') cur = cur -> left;else cur = cur -> right;if(!cur -> left && !cur -> right){ans += cur -> data;cur = root;}}return ans;
}signed main()
{cin >> s >> d;vet.push_back({"",""});for(int i = 1; i <= s; i++){string a, b;cin >> a >> b;vet.push_back({a, b});}string ss;cin >> ss;int index = 0;root = rebuildHuffmanTree(ss, index);cin >> n;for(int i = 1; i <= n; i++){int x;int y;cin >> x >> y;if(x == 1){pii ans;if(y <= s) ans = vet[y];else ans = deq[y - s - 1];cout << ans.first << ": " << ans.second << endl;}if(x == 2){if(y != 0 ){string str;cin >> str;string ans2 = str;string t = str.substr(0, 2);if(t == "HH")ans2 = str.erase(0,1);else if(t[0] == 'H'){str.erase(0, 1);ans2 = f1(str);}string ans;if(y <= s) ans = vet[y].first;else ans = deq[y - s - 1].first;cout << ans << ": " << ans2 << endl;deq.push_front({ans, ans2});}if(y == 0){string s1, s2;cin >> s1 >> s2;string t1 = s1.substr(0, 2), t2 = s2.substr(0, 2);string ans1 = s1, ans2 = s2; if(t1 == "HH") ans1 = s1.erase(0, 1);else if(t1[0] == 'H'){s1.erase(0, 1);ans1 = f1(s1);}if(t2 == "HH") ans2 = s2.erase(0, 1);else if(t2[0] == 'H'){s2.erase(0, 1);ans2 = f1(s2);}cout << ans1 << ": " << ans2 << endl;deq.push_front({ans1, ans2}); }}if(x == 3 ){if(y != 0){string str;cin >> str;string ans2 = str;string t = str.substr(0, 2);if(t == "HH")ans2 = str.erase(0,1);else if(t[0] == 'H'){str.erase(0, 1);ans2 = f1(str);}string ans;if(y <= s) ans = vet[y].first;else ans = deq[y - s - 1].first;cout << ans << ": " << ans2 << endl;}if(y == 0){string s1, s2;cin >> s1 >> s2;string t1 = s1.substr(0, 2), t2 = s2.substr(0, 2);string ans1 = s1, ans2 = s2;if(t1 == "HH") ans1 = s1.erase(0, 1);else if(t1[0] == 'H'){s1.erase(0, 1);ans1 = f1(s1);}if(t2 == "HH") ans2 = s2.erase(0, 1);else if(t2[0] == 'H'){s2.erase(0, 1);ans2 = f1(s2);}cout << ans1 << ": " << ans2 << endl;}}}
}
造题计划(上)
时间限制: 1.0 秒
空间限制: 512 MiB
相关文件: 题目目录
题目背景
西西艾弗大学的第三十九届算法考试就要开始了,小 C 正在紧张地造题……
题目描述
小 C 想到了这样一个题目:给定一棵 nn 个结点的树,要求选手给每个结点分配一个权值,使得树上所有点的权值构成一个 00 到 n−1n−1 的排列,并满足 mm 条限制,每条限制形如 (x,y,v)(x,y,v),表示树上从 xx 到 yy 路径上所有点的权值构成集合的 mexmex 值等于 vv。
小 C 已经想到了绝妙的正解做法,现在他需要你帮他写SPJ的核心部分。具体而言,你会接收到一棵 nn 个结点的树,其中编号为 ii 的点有权值 aiai,且 a1,a2,…,ana1,a2,…,an构成一个 00 到 n−1n−1 的排列。接下来你要依次检验 mm 条限制,每条限制会给定树上的两个结点 xx 和 yy,你需要求出从 xx 到 yy 的简单路径上所有点的权值构成集合的 mexmex 值。
对于一个集合 SS,mex(S)mex(S) 表示最小的没有在SS中出现过的自然数。例如:
-
mex(0,1,2,5)=3mex(0,1,2,5)=3
-
mex(1,2)=0mex(1,2)=0
输入格式
从标准输入读入数据。
输入的第一行包含两个正整数 n,mn,m,分别表示用于结点数和限制条数。
第二行包含 nn 个正整数 a1,a2,…,ana1,a2,…,an,表示每个结点的权值。
接下来 n−1n−1 行每行包含两个整数,表示树上的一条边。
接下来 mm 行每行包含两个整数 xi,yixi,yi,表示一条限制。你需要求出从 xixi 到 yiyi 的简单路径上所有点的权值构成集合的 mexmex 值。
输出格式
输出到标准输出。
输出 mm 行每行一个正整数,第 ii 行的表示第 ii 条限制所需要求的 mexmex 值。
样例1输入
7 3
1 0 2 6 3 4 5
1 2
2 5
2 6
1 3
3 7
1 4
5 3
6 3
7 4
样例1输出
4
3
0
子任务
本题采用捆绑测试,你只有通过一个子任务中的所有测试点才能得到该子任务的分数。
- 子任务一(2020分):n,m≤1000n,m≤1000;
- 子任务二(2020分):保证给定的树是一条链;
- 子任务三(3030分):n,m≤5×104n,m≤5×104;
- 子任务四(3030分):无特殊限制。
对于所有数据,保证 1≤n,m≤2×1051≤n,m≤2×105。a1,a2,…,ana1,a2,…,an 构成一个 00 到 n−1n−1 的排列。
第一个测试点暴力
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n, m;
const int N = 2e5 + 10, M = N * 2;
int w[N], depth[N], st[N];
int h[N], ne[M], idx, e[M];
vector<int> vet;
int dist[N], pre[N];
typedef pair<int, int> pii;
set<int> s1, s2;
void add(int a, int b)
{e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dij(int a, int b)
{s2.clear();memset(pre, -1, sizeof(pre));priority_queue<pii,vector<pii>,greater<pii>>heap;memset(dist,0x3f,sizeof(dist));dist[a]=0;memset(st, 0, sizeof(st));heap.push({0,a});while(!heap.empty()){auto t=heap.top();heap.pop();int ver=t.second,distance=t.first;// cout<<ver<<" ";if(!st[ver]){st[ver]=1;for(int i=h[ver];i!=-1;i=ne[i]){int j=e[i];// dist[j]=min(dist[j],dist[ver]+w[i]);// heap.push({dist[j],j});if (dist[j] > dist[ver] + 1){dist[j] = dist[ver] + 1;pre[j] = ver;heap.push({dist[j], j});}}}}int i = b;s2.insert(w[i]);while(pre[i] != -1) {i = pre[i];s2.insert(w[i]);}int j = 0;while(true){if(s2.count(j)) j++;else break;}cout << j << endl;}
signed main()
{cin >> n >> m;for(int i = 1; i <= n; i++) h[i] = -1;for(int i = 1; i <= n; i++) cin >> w[i];for(int i = 1; i < n; i++) {int a, b;cin >> a >> b;add(a, b), add(b, a);}for(int i = 1; i <= m; i++){int a, b;cin >> a >> b;dij(a, b);}
}
第二个测试点特殊样例,莫队写法
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>using namespace std;// 节点和查询的最大数量
const int MAXN = 200005;// n: 节点数, m: 查询数
int n, m;
// a[i]: 节点 i 的权值 (1-indexed)
int a[MAXN];
// adj: 树的邻接表
vector<int> adj[MAXN];// b[i]: 链上第 i 个位置的节点的权值
int b[MAXN];
// pos[i]: 节点 i 在链上的位置
int pos[MAXN];// --- Mo's Algorithm 所需变量 ---
// count_val[v]: 权值 v 在当前窗口中出现的次数
int count_val[MAXN];
// current_mex: 当前窗口的 mex 值
int current_mex = 0;
// block_size: Mo's 算法的分块大小
int block_size;// 查询结构体
struct Query {int l, r, id;
};// Mo's 算法的排序比较函数
// (使用奇偶排序优化)
bool compareQueries(const Query& q1, const Query& q2) {int block1 = q1.l / block_size;int block2 = q2.l / block_size;if (block1 != block2) {return block1 < block2;}// 如果在同一个块,奇数块R递增,偶数块R递减if (block1 % 2 == 1) {return q1.r < q2.r;} else {return q1.r > q2.r;}
}// Mo's: 向窗口添加一个值
void add(int val) {count_val[val]++;// 如果添加的是当前的 mex,我们需要找到下一个 mexwhile (count_val[current_mex] > 0) {current_mex++;}
}// Mo's: 从窗口移除一个值
void remove(int val) {count_val[val]--;// 如果移除后这个值的计数变为0,并且它比当前mex小// 那么它就是新的 mexif (count_val[val] == 0 && val < current_mex) {current_mex = val;}
}int main() {// 优化 C++ IOios::sync_with_stdio(false);cin.tie(NULL);cin >> n >> m;// 读取权值for (int i = 1; i <= n; ++i) {cin >> a[i];}// 读取 n-1 条边for (int i = 0; i < n - 1; ++i) {int u, v;cin >> u >> v;adj[u].push_back(v);adj[v].push_back(u);}// --- 1. 树转链 ---int start_node = 1;// 找到链的一个端点 (度为1的节点)// 注意处理 n=1 的特殊情况if (n > 1) {for (int i = 1; i <= n; ++i) {if (adj[i].size() == 1) {start_node = i;break;}}}// 从端点开始遍历,构建 b[] 和 pos[]int curr = start_node;int prev = -1; // 记录上一个节点,防止往回走for (int i = 1; i <= n; ++i) {b[i] = a[curr]; // 记录链上第i个位置的权值pos[curr] = i; // 记录节点curr在链上的位置int next_node = -1;for (int neighbor : adj[curr]) {if (neighbor != prev) {next_node = neighbor;break;}}prev = curr;curr = next_node;}// --- 2. 准备 Mo's 算法 ---block_size = (int)sqrt(n);if (block_size == 0) block_size = 1; // 避免n=0时除以0vector<Query> queries;vector<int> answers(m);// 读取查询并将其转化为链上的区间for (int i = 0; i < m; ++i) {int x, y;cin >> x >> y;int l = pos[x];int r = pos[y];if (l > r) {swap(l, r); // 保证 l <= r}queries.push_back({l, r, i});}// --- 3. 运行 Mo's 算法 ---sort(queries.begin(), queries.end(), compareQueries);int mo_l = 1, mo_r = 0; // 当前窗口 [mo_l, mo_r]for (const auto& q : queries) {// 扩展窗口while (mo_r < q.r) {mo_r++;add(b[mo_r]);}while (mo_l > q.l) {mo_l--;add(b[mo_l]);}// 缩小窗口while (mo_r > q.r) {remove(b[mo_r]);mo_r--;}while (mo_l < q.l) {remove(b[mo_l]);mo_l++;}// 此时 current_mex 就是查询 q 的答案answers[q.id] = current_mex;}// --- 4. 输出答案 ---for (int i = 0; i < m; ++i) {cout << answers[i] << "\n";}return 0;
}
1,3能通过,树上莫队 50分代码
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>using namespace std;const int N = 2e5 + 10, M = 2e5 + 10;;struct Query
{int id, l, r, p;
}q[M];int n, m, len;
int h[N], e[N * 2], w[N * 2], ne[N * 2], idx;
int que[N], d[N], fa[N][16];
int seq[N * 2], first[N], last[N], top;
int st[N], cnt[N];
int res[M], id[N * 2];
int count_val[N], current_mel;
vector<int> nums; int find(int x)
{int l = 0, r = nums.size() - 1;while(l < r){int mid = l + r + 1 >> 1;if(nums[mid] <= x) l = mid;else r = mid - 1;}return r;
}void add_edge(int a, int b)
{e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}void dfs(int u, int fa)
{seq[++top] = u;first[u] = top;for(int i = h[u]; i != -1; i = ne[i]){int j = e[i];if(j == fa) continue;dfs(j, u);}seq[++top] = u;last[u] = top;
}void bfs() //预处理 d[], fa[][]
{memset(d, 0, sizeof d);d[1] = 1;int hh = 0, tt = 0;que[0] = 1;while(hh <= tt){int t = que[hh++];for(int i = h[t]; i != -1; i = ne[i]){int j = e[i];if(d[j]) continue;d[j] = d[t] + 1;fa[j][0] = t;for(int k = 1; k <= 15; k++) fa[j][k] = fa[fa[j][k - 1]][k - 1];que[++tt] = j;}}
}int lca(int a, int b) //计算 a 和 b 的最近公共祖先
{if(d[a] < d[b]) swap(a, b);for(int k = 15; k >= 0; k--)if(d[fa[a][k]] >= d[b])a = fa[a][k];if(a == b) return a;for(int k = 15; k >= 0; k--)if(fa[a][k] != fa[b][k]){a = fa[a][k];b = fa[b][k];}return fa[a][0];
}//先按照左端点所在块编号从小到大排序,再按照右端点从小到大排序
bool cmp(const Query &a, const Query &b)
{if(id[a.l] != id[b.l]) return id[a.l] < id[b.l];return a.r < b.r;
}void add(int x, int ¤t_mex) // x 是节点编号
{int val = w[x]; // 获取节点 x 的权值st[x] ^= 1; // 切换节点 x 的"在/不在"路径集中的状态if(st[x] == 1) // 节点 x 被加入路径集{count_val[val]++; // 对应权值的计数+1// 如果我们刚加入的数是当前的 mex,我们需要找到下一个while (count_val[current_mex] > 0) {current_mex++;}}else // 节点 x 被移出路径集{count_val[val]--; // 对应权值的计数-1// 如果我们移出的数导致其计数变为0,并且它比当前mex小// 那么它就是新的 mexif (count_val[val] == 0 && val < current_mex) {current_mex = val;}}
}
int main()
{scanf("%d%d", &n, &m);for(int i = 1; i <= n; i++){scanf("%d", &w[i]);nums.push_back(w[i]);}//离散化sort(nums.begin(), nums.end());nums.erase(unique(nums.begin(), nums.end()), nums.end());for(int i = 1; i <= n; i++) w[i] = find(w[i]); //将每个数替换成离散化后的数memset(h, -1, sizeof h); //初始化邻接表for(int i = 0; i < n - 1; i++){int a, b;scanf("%d%d", &a, &b);add_edge(a, b), add_edge(b, a); //添加无向边}dfs(1, -1); //深搜预处理 seq[], first[], last[]bfs(); //预处理 d[], fa[][]for(int i = 0; i < m; i++){int a, b;scanf("%d%d", &a, &b);if(first[a] > first[b]) swap(a, b); //保证 first[a] < first[b];int p = lca(a, b); //计算 a 和 b 的最近公共祖先if(a == p) q[i] = {i, first[a], first[b], 0}; //如果 a == p,则查询区间为 [first[a], first[b]]else q[i] = {i, last[a], first[b], p}; //如果 a != p,则查询区间为 [last[a], first[b]] + p}int len = sqrt(top); //计算块的长度for(int i = 1; i <= top; i++) id[i] = (i - 1) / len; //计算每个下标所在的块编号//将所有询问先按照左端点所在块编号从小到大排序,再按照右端点从小到大排序sort(q, q + m, cmp);//莫队算法for(int k = 0, i = 1, j = 0, ans = 0; k < m; k++){int num = q[k].id, l = q[k].l, r = q[k].r, p = q[k].p;//由于加入操作和删除操作写法相同,因此不再重复写while(j < r) add(seq[++j], ans); //j 右移,加入操作while(j > r) add(seq[j--], ans); //j 左移,删除操作while(i < l) add(seq[i++], ans); //i 右移,删除操作while(i > l) add(seq[--i], ans); //i 左移,加入操作if(p) add(p, ans); //如果 p != 0,则需要将 p 加入res[num] = ans; //记录答案if(p) add(p, ans); //还原}for(int i = 0; i < m; i++) printf("%d\n", res[i]);return 0;
}
综合了一下, 70分代码
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <set> using namespace std;inline int read() {int x = 0, f = 1;char ch = getchar();while (ch < '0' || ch > '9') {if (ch == '-') f = -1;ch = getchar();}while (ch >= '0' && ch <= '9') {x = x * 10 + ch - '0';ch = getchar();}return x * f;
}inline void write(int x) {if (x < 0) {putchar('-');x = -x;}if (x > 9) {write(x / 10);}putchar(x % 10 + '0');
}inline void writeln(int x) {write(x);putchar('\n');
}const int N = 2e5 + 10, M = 2e5 + 10, MAXN = 2e5 + 10;struct Query
{int id, l, r, p;
} q[M];
int block_size;
vector<int> adj[MAXN];
int b[MAXN];
int pos[MAXN]; int n, m, len;
int h[N], e[N * 2], ne[N * 2], idx;
int w[N];
int que[N], d[N], fa[N][18];
int seq[N * 2], first[N], last[N], top;
int st[N];
int res[M], id[N * 2];
int count_val[N]; bool compareQueries(const Query& q1, const Query& q2) {int block1 = q1.l / block_size;int block2 = q2.l / block_size;if (block1 != block2) {return block1 < block2;}if (block1 % 2 == 1) {return q1.r < q2.r;} else {return q1.r > q2.r;}
}void add_edge(int a, int b)
{e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}void dfs(int u, int pre)
{seq[++top] = u;first[u] = top;for (int i = h[u]; i != -1; i = ne[i]){int j = e[i];if (j == pre) continue;d[j] = d[u] + 1; fa[j][0] = u; dfs(j, u);}seq[++top] = u;last[u] = top;
}void init_lca()
{for (int k = 1; k <= 17; k++) for (int i = 1; i <= n; i++)fa[i][k] = fa[fa[i][k - 1]][k - 1];
}int lca(int a, int b)
{if (d[a] < d[b]) swap(a, b);for (int k = 17; k >= 0; k--)if (d[fa[a][k]] >= d[b])a = fa[a][k];if (a == b) return a;for (int k = 17; k >= 0; k--)if (fa[a][k] != fa[b][k]){a = fa[a][k];b = fa[b][k];}return fa[a][0];
}bool cmp(const Query &a, const Query &b)
{if (id[a.l] != id[b.l]) return id[a.l] < id[b.l];if (id[a.l] & 1) return a.r < b.r;return a.r > b.r;
}void add(int x, int ¤t_mex)
{int val = w[x]; st[x] ^= 1; if (st[x] == 1) {count_val[val]++;while (count_val[current_mex] > 0) {current_mex++;}}else {count_val[val]--;if (count_val[val] == 0 && val < current_mex) {current_mex = val;}}
}void add2(int val, int ¤t_mex) {count_val[val]++;while (count_val[current_mex] > 0) {current_mex++;}
}void remove(int val, int ¤t_mex) {count_val[val]--;if (count_val[val] == 0 && val < current_mex) {current_mex = val;}
}int main()
{n = read();m = read();for (int i = 1; i <= n; i++){w[i] = read();}memset(h, -1, sizeof h); for (int i = 0; i < n - 1; i++){int a = read(), b = read();add_edge(a, b), add_edge(b, a); adj[a].push_back(b);adj[b].push_back(a);}set<int> st_degree_1;int start_node = 1;if (n > 1) {for (int i = 1; i <= n; ++i) {if (adj[i].size() == 1) {start_node = i;st_degree_1.insert(i);}}}// 如果 n=1, st_degree_1.size() == 0// 如果 n>1 且是链, st_degree_1.size() == 2if (st_degree_1.size() == 2){int curr = start_node;int prev = -1; for (int i = 1; i <= n; ++i) {b[i] = w[curr]; pos[curr] = i; int next_node = -1;for (int neighbor : adj[curr]) {if (neighbor != prev) {next_node = neighbor;break;}}prev = curr;curr = next_node;}block_size = (int)sqrt(n);if (block_size == 0) block_size = 1; for (int i = 0; i < m; ++i) {int x = read(), y = read();int l = pos[x];int r = pos[y];if (l > r) {swap(l, r); }q[i] = {i, l, r, 0}; }sort(q, q + m, compareQueries); int mo_l = 1, mo_r = 0; int current_mex = 0; for (int k = 0; k < m; k++) {int l = q[k].l, r = q[k].r, id = q[k].id;while (mo_r < r) add2(b[++mo_r], current_mex);while (mo_l > l) add2(b[--mo_l], current_mex);while (mo_r > r) remove(b[mo_r--], current_mex);while (mo_l < l) remove(b[mo_l++], current_mex);res[id] = current_mex; }for (int i = 0; i < m; ++i) {writeln(res[i]); }return 0; }d[1] = 1; fa[1][0] = 0; dfs(1, 0); init_lca(); for (int i = 0; i < m; i++){int a = read(), b = read();if (first[a] > first[b]) swap(a, b); int p = lca(a, b); if (a == p) q[i] = {i, first[a], first[b], 0}; else q[i] = {i, last[a], first[b], p}; }len = top / sqrt(m * 2.0 / 3.0); if (len == 0) len = 1;for (int i = 1; i <= top; i++) id[i] = (i - 1) / len; sort(q, q + m, cmp);int i = 1, j = 0, current_mex = 0; for (int k = 0; k < m; k++){int num = q[k].id, l = q[k].l, r = q[k].r, p = q[k].p;while (j < r) add(seq[++j], current_mex);while (j > r) add(seq[j--], current_mex);while (i < l) add(seq[i++], current_mex);while (i > l) add(seq[--i], current_mex);if (p) add(p, current_mex); res[num] = current_mex; if (p) add(p, current_mex); }for (int i = 0; i < m; i++) {writeln(res[i]);}return 0;
}
gemini生成的 可持久化线段树100分代码
#include <iostream>
#include <vector>
#include <algorithm>using namespace std;const int N = 2e5 + 10; // 节点数
const int LOGN = 19; // 2^18 > 2e5int n, m;
int w[N]; // w[i]: 节点 i 的权值
vector<int> g[N]; // 邻接表存树// --- LCA (倍增法) ---
int dep[N];
int fa[N][LOGN]; // fa[i][j]: i 的第 2^j 个祖先// --- 可持久化线段树 (主席树) ---
struct Node {int l_son, r_son; // 左右儿子节点的编号int sum; // 区间 [l, r] 内权值的出现次数
} tr[N * LOGN * 2]; // N 个版本, 每次插入 logN 个节点, *2 保险int root[N]; // root[i]: 节点 i 对应的主席树根节点
int cnt; // 主席树节点计数器// 插入: 在 old_rt 版本的基础上, 将 val 的计数+1, 返回新版本根节点
int insert(int old_rt, int l, int r, int val) {int new_rt = ++cnt; // 创建新节点tr[new_rt] = tr[old_rt]; // 复制旧节点信息tr[new_rt].sum++; // 计数+1if (l == r) return new_rt;int mid = (l + r) >> 1;if (val <= mid) {tr[new_rt].l_son = insert(tr[old_rt].l_son, l, mid, val);} else {tr[new_rt].r_son = insert(tr[old_rt].r_son, mid + 1, r, val);}return new_rt;
}// DFS: 预处理 LCA 数组 和 构建主席树
void dfs(int u, int p) {dep[u] = dep[p] + 1;fa[u][0] = p;// 1. 构建 u 对应的主席树版本// root[0] (p=0时) 是空树, 编号为 0, tr[0] 全为 0root[u] = insert(root[p], 0, n, w[u]); // (值域是 [0, n], 因为 mex 可能为 n)// 2. 预处理 LCA 的 fa 数组for (int i = 1; i < LOGN; i++) {fa[u][i] = fa[fa[u][i - 1]][i - 1];}// 3. 递归子节点for (int v : g[u]) {if (v == p) continue;dfs(v, u);}
}// LCA 查询
int query_lca(int x, int y) {if (dep[x] < dep[y]) swap(x, y);for (int i = LOGN - 1; i >= 0; i--) {if (dep[fa[x][i]] >= dep[y]) {x = fa[x][i];}}if (x == y) return x;for (int i = LOGN - 1; i >= 0; i--) {if (fa[x][i] != fa[y][i]) {x = fa[x][i];y = fa[y][i];}}return fa[x][0];
}// 在 4 棵树代表的路径上查询 mex
// x_rt, y_rt, l_rt, p_rt 分别是 x, y, lca, fa[lca] 对应的树根
int query_mex(int x_rt, int y_rt, int l_rt, int p_rt, int l, int r) {if (l == r) {return l; // 找到了第一个空位}int mid = (l + r) >> 1;// 计算左子树 [l, mid] 范围内的权值总数int sum_left = tr[tr[x_rt].l_son].sum + tr[tr[y_rt].l_son].sum - tr[tr[l_rt].l_son].sum - tr[tr[p_rt].l_son].sum;int expected_left_count = mid - l + 1; // 左子树区间的长度if (sum_left < expected_left_count) {// 左子树 [l, mid] 有空位, mex 必定在左侧return query_mex(tr[x_rt].l_son, tr[y_rt].l_son, tr[l_rt].l_son, tr[p_rt].l_son, l, mid);} else {// 左子树已满, mex 必定在右侧return query_mex(tr[x_rt].r_son, tr[y_rt].r_son, tr[l_rt].r_son, tr[p_rt].r_son, mid + 1, r);}
}int main() {// 启用快速IOios::sync_with_stdio(0); cin.tie(0); cout.tie(0);cin >> n >> m;for (int i = 1; i <= n; i++) {cin >> w[i];}for (int i = 0; i < n - 1; i++) {int u, v;cin >> u >> v;g[u].push_back(v);g[v].push_back(u);}// 从 1 号点开始, 父亲为 0, 深度为 0 (tr[0] 和 root[0] 都默认为 0)dfs(1, 0);while (m--) {int x, y;cin >> x >> y;int l = query_lca(x, y);int p = fa[l][0];// 在值域 [0, n] 上查询 mexint mex = query_mex(root[x], root[y], root[l], root[p], 0, n);cout << mex << "\n";}return 0;
}
造题计划(下)
时间限制: 5.0 秒
空间限制: 512 MiB
相关文件: 题目目录
题目背景
西西艾弗大学的第三十九届算法考试就要开始了,小 C 和 小 F 二人正在紧张地筹备题目……
题目描述
小 C 和 小 F 需要在 nn 天时间内造出若干道题目用于考试。二人的分工很明确:小 C 负责造题,小 F 负责验题。显然,一个题需要先被小 C 造完,才能被小 F 验,验完之后才能上线考试。
西西艾弗大学有合理的工作制度,每一天,小 C 会在上午造题,在小 C 造完题下班之后,小 F 会在下午验题。因为不需要打卡,他们可以灵活地选择这一天是否工作。
小 C 和小 F 都很注重工作和生活的平衡,所以小 C 一天最多出一个题,小 F 一天最多验一个题。因为人的工作状态有波动,在第 ii 天,小 C 造一个题需要的精力值为 aiai,小 F 验一个题需要的精力值为 bibi。
小 C 和 小 F 还要一起完成某门课的大作业,所以他们不能把全部的精力都花在造题上。根据二人精准的测算,他们用于出题的总精力值不可以大于 mm。
现在小 C 和 小 F 想知道,在这 nn 天里他们最多能造出多少题目。
输入格式
从标准输入读入数据。
输入的第一行包含两个正整数 n,mn,m,分别表示用于筹备比赛的天数和可以花费的最大总精力值。
第二行包含 nn 个正整数 a1,a2,…,ana1,a2,…,an,表示每天小 C 造题需要花费的精力。
第三行包含 nn 个正整数 b1,b2,…,bnb1,b2,…,bn,表示每天小 F 验题需要花费的精力。
输出格式
输出到标准输出。
输出一行一个正整数,表示小 C 和 小 F 最多能造出的题目数量。
样例1输入
5 12
3 5 7 2 9
4 2 8 3 7
样例1输出
2
样例1解释
小 C 在第一天和第四天造题。
小 F 在第二天和第四天验题。
子任务
本题采用捆绑测试,你只有通过一个子任务中的所有测试点才能得到该子任务的分数。
- 子任务一(2020分):n≤100n≤100;
- 子任务二(3030分):n≤3000n≤3000;
- 子任务三(2020分):n≤105n≤105;
- 子任务四(3030分):无特殊限制。
对于所有数据,保证 1≤n≤5×105,1≤ai,bi≤109,1≤m≤10181≤n≤5×105,1≤ai,bi≤109,1≤m≤1018。
20分 dp写法 ,自己没写出来,ai修改后的。
#include <iostream>
#include <vector>
#include <algorithm>using namespace std;// 定义一个非常大的数表示无穷大
const long long INF = 2e18; int main() {int n;long long m;cin >> n >> m;// 使用 1-based 索引,所以大小为 n+1vector<long long> a(n + 1);vector<long long> b(n + 1);for (int i = 1; i <= n; ++i) {cin >> a[i];}for (int i = 1; i <= n; ++i) {cin >> b[i];}// dp[i][j][k]// i: 天数 (0 到 n)// j: 待验数 (0 到 n)// k: 完工数 (0 到 n)vector<vector<vector<long long>>> dp(n + 1, vector<vector<long long>>(n + 1, vector<long long>(n + 1, INF)));// 初始状态:第 0 天,0 待验,0 完工,花费为 0dp[0][0][0] = 0;// 循环遍历每一天for (int i = 1; i <= n; ++i) {// 遍历所有可能的待验数 jfor (int j = 0; j <= i; ++j) {// 遍历所有可能的完工数 k// 优化:j + k (总共 C 生产的) 不可能超过 ifor (int k = 0; k + j <= i; ++k) {// 1. 两人都不工作 (来自 dp[i-1][j][k])dp[i][j][k] = dp[i-1][j][k];// 2. 只有 C 工作 (来自 dp[i-1][j-1][k])if (j > 0) {dp[i][j][k] = min(dp[i][j][k], dp[i-1][j - 1][k] + a[i]);}// 3. 只有 F 工作 (来自 dp[i-1][j+1][k-1])// (前一天有 j+1 个待验, 完工 k-1 个)if (k > 0) {dp[i][j][k] = min(dp[i][j][k], dp[i-1][j + 1][k - 1] + b[i]);}// 4. 两人都工作 (来自 dp[i-1][j][k-1])// (C 生产1个, F 消耗1个, 待验数 j 不变, 完工 k-1 -> k)if (k > 0) {dp[i][j][k] = min(dp[i][j][k], dp[i-1][j][k - 1] + a[i] + b[i]);}}}}// 查找答案int max_tasks = 0;// 从 n 完工 (最好情况) 向 0 完工 (最差情况) 检查for (int k = n; k >= 0; --k) {// 检查在完工 k 个的情况下,是否存在一个合法的待验数 j// 使得总花费 <= mfor (int j = 0; j <= n; ++j) {if (dp[n][j][k] <= m) {// 找到了,因为 k 是从大到小遍历的,// 这一定是最大完工数max_tasks = k;goto end_loop; // 跳出两层循环}}}end_loop:cout << max_tasks << endl;return 0;
}
