第一题:拼数 (number)
思路分析
题目要求使用一个字符串中包含的数字字符拼成一个最大的正整数。
要组成最大的数,我们的策略应该是把越大的数字放在越高的位(即越靠前的位置)。例如,用数字 \(9, 2, 1, 0, 0\) 能拼成的最大数就是 \(92100\)。
因此,解题思路非常直接:
- 从输入的字符串中提取出所有的数字字符。
- 将这些数字字符作为一个字符集合,进行降序排列('9' > '8' > ... > '0')。
- 将排序后的数字字符依次输出,拼接成最终的字符串,即为所求的最大正整数。
AC 代码
#include <bits/stdc++.h>using namespace std;// 用于文件输入输出重定向
void setup_io() {freopen("number.in", "r", stdin);freopen("number.out", "w", stdout);
}int main() {// 提高C++ I/O效率ios_base::sync_with_stdio(false);cin.tie(NULL);setup_io();string s;cin >> s;vector<char> digits;// 遍历输入字符串,提取所有数字字符for (char c : s) {if (isdigit(c)) {digits.push_back(c);}}// 将数字字符按从大到小的顺序排序sort(digits.begin(), digits.end(), greater<char>());// 依次输出排序后的数字for (char d : digits) {cout << d;}cout << endl;return 0;
}
第二题:座位 (seat)
思路分析
题目要求根据考生的成绩确定其在一个 \(n\) 行 \(m\) 列考场中的“蛇形”座位。座位排布规则为:
- 第1, 3, 5, ... (奇数)列,从上到下排(行号 1 -> n)。
- 第2, 4, 6, ... (偶数)列,从下到上排(行号 n -> 1)。
解题步骤如下:
- 读入 \(n\), \(m\) 和全部 \(n*m\) 个考生的成绩。根据题目和样例,输入的第一个成绩 \(a1\) 就是我们要找的目标考生的成绩。
- 将所有成绩进行降序排序,以确定每个成绩的排名。
- 在排序后的列表中找到目标考生成绩的位置。这个位置就是他的排名(从0开始计数),我们称之为 \(rank\)。
- 根据排名 \(rank\) 计算座位的行列号(均从0开始计数):
- 考生所在的列号 \(col_0 = rank / n\)。
- 考生在该列中的位置(从该列的起始方向数起)\(pos_in_col = rank % n\)。
- 根据列号的奇偶性,结合 \(pos_in_col\) 计算最终的行号:
- 如果 \(col_0\) 是偶数(对应第1, 3...列),方向是从上到下,所以行号 \(row_0 = pos_in_col\)。
- 如果 \(col_0\) 是奇数(对应第2, 4...列),方向是从下到上,所以行号 \(row_0 = n - 1 - pos_in_col\)。
- 最后,将从0开始的行列号 \(col_0\), \(row_0\) 分别加1,得到题目要求的从1开始的行列号并输出。
AC 代码
#include <bits/stdc++.h>using namespace std;void setup_io() {freopen("seat.in", "r", stdin);freopen("seat.out", "w", stdout);
}int main() {ios_base::sync_with_stdio(false);cin.tie(NULL);setup_io();int n, m;cin >> n >> m;int total_students = n * m;vector<int> scores(total_students);for (int i = 0; i < total_students; ++i) {cin >> scores[i];}// 根据题意,输入的第一个成绩 a1 是小 R 的成绩int target_score = scores[0];// 复制一份用于排序,以确定排名vector<int> sorted_scores = scores;sort(sorted_scores.begin(), sorted_scores.end(), greater<int>());// 找到目标分数的排名 (从0开始)int rank = -1;for (int i = 0; i < total_students; ++i) {if (sorted_scores[i] == target_score) {rank = i;break;}}// 计算0-indexed的列号和行号int col_0 = rank / n;int pos_in_col = rank % n;int row_0;if (col_0 % 2 == 0) { // 奇数列 (1, 3, ...),从上到下row_0 = pos_in_col;} else { // 偶数列 (2, 4, ...),从下到上row_0 = n - 1 - pos_in_col;}// 输出1-indexed的结果cout << col_0 + 1 << " " << row_0 + 1 << endl;return 0;
}
第三题:异或和 (xor)
思路分析
题目要求在一个序列中找出最多数量的、互不相交的、且区间内所有元素的异或和都等于 \(k\) 的区间。
这是一个经典的动态规划问题。为了高效地计算任意区间的异或和,我们可以使用 前缀异或和。
- 令 \(prefix\_xor[i]\) 表示序列 \(a\) 从第1个元素到第 \(i\) 个元素的异或和。
- 那么,区间 \([j, i]\) 的异或和就是 \(prefix\_xor[i] \oplus prefix\_xor[j-1]\)。
- 我们要求 \(prefix\_xor[i] \oplus prefix\_xor[j-1] = k\),这等价于 \(prefix\_xor[j-1] = prefix\_xor[i] \oplus k\)。
现在定义 \(dp[i]\) 为:考虑序列的前 \(i\) 个元素,所能选出的最大满足条件的区间数量。
在计算 \(dp[i]\) 时,有两种选择:
- 不选择以 \(i\) 结尾的任何区间:此时 \(dp[i] = dp[i-1]\)。
- 选择一个以 \(i\) 结尾的区间 \([j, i]\):此时,我们能得到的区间总数是 \(1 + dp[j-1]\)。
为避免低效地遍历所有可能的 \(j\),我们用一个 \(map\) (哈希表) \(max\_dp\_for\_pxor\) 来优化查找。\(max\_dp\_for\_pxor[val]\) 记录的是:当某个前缀异或和为 \(val\) 时,其对应的最大 \(dp\) 值。
最终算法:
- 初始化 \(dp\) 数组为0,\(dp[0]=0\)。初始化 \(map\),放入 \(max\_dp\_for\_pxor[0] = 0\) (对应空前缀)。
- 遍历 \(i\) 从 1 到 \(n\):
a. \(dp[i]\) 首先继承 \(dp[i-1]\) 的值。
b. 计算当前的前缀异或和 \(current_pxor\),并找到需要匹配的目标前缀和 \(target = current\_pxor \oplus k\)。
c. 如果 \(map\) 中存在 \(target\),说明可以构成一个以 \(i\) 结尾的有效区间。用 \(max\_dp\_for\_pxor[target] + 1\) 来尝试更新 \(dp[i]\)。
d. 用 \(dp[i]\) 的值更新 \(map\) 中关于 \(current\_pxor\) 的记录,以便后续的计算使用。 - 最终答案为 \(dp[n]\)。
AC 代码
#include <bits/stdc++.h>using namespace std;void setup_io() {freopen("xor.in", "r", stdin);freopen("xor.out", "w", stdout);
}int main() {ios_base::sync_with_stdio(false);cin.tie(NULL);setup_io();int n;int k;cin >> n >> k;vector<int> a(n);for (int i = 0; i < n; ++i) {cin >> a[i];}vector<int> dp(n + 1, 0);// map[pxor] = max_dp_value, 记录前缀异或和为pxor时,能达到的最大dp值map<int, int> max_dp_for_pxor;max_dp_for_pxor[0] = 0; // 空前缀异或和为0,dp值为0int current_prefix_xor = 0;for (int i = 1; i <= n; ++i) {current_prefix_xor ^= a[i - 1];// 策略1: 不选择以 a[i-1] 结尾的区间dp[i] = dp[i - 1];// 策略2: 寻找一个以 a[i-1] 结尾的合法区间int target_pxor = current_prefix_xor ^ k;if (max_dp_for_pxor.count(target_pxor)) {dp[i] = max(dp[i], max_dp_for_pxor[target_pxor] + 1);}// 更新 map,为后续的计算提供信息if (max_dp_for_pxor.count(current_prefix_xor)) {max_dp_for_pxor[current_prefix_xor] = max(max_dp_for_pxor[current_prefix_xor], dp[i]);} else {max_dp_for_pxor[current_prefix_xor] = dp[i];}}cout << dp[n] << endl;return 0;
}
第四题:多边形 (polygon)
思路分析
题目要求计算能拼成多边形的木棍子集的数量。条件是子集大小 \(m >= 3\) 且所有木棍长度和 \(S\) 大于最长木棍 \(L\) 的两倍 (\(S > 2L\))。这个条件等价于 最长边的长度必须小于其余各边长度之和。
这是一个组合计数问题,可以使用动态规划解决,其思想类似于0-1背包。
算法步骤:
- 将所有木棍按长度从小到大排序。这是关键,它使得我们在遍历时,可以方便地将当前木棍 \(a[i]\) 视为最长边,而只从它之前的木棍 \(a[0]...a[i-1]\) 中进行选择。
- 我们用一个 \(dp\) 数组,\(dp[s]\) 表示从当前已处理过的木棍中,选出子集使其长度和为 \(s\) 的方案数。
- 整体流程如下:
a. 对木棍数组 \(a\) 排序。
b. 初始化 \(dp\) 数组,\(dp[0] = 1\)(代表和为0的方案只有一种,即空集)。
c. 遍历 \(i\) 从 0 到 \(n-1\),将 \(a[i]\) 视为当前考虑的“最长边”:
i. 在处理 \(a[i]\) 之前,\(dp\) 数组中存储的是从 \({a[0], ..., a[i-1]}\) 中选择子集的方案。
ii. 我们需要计算从这个前缀集合中选出子集,其和大于 \(a[i]\) 的方案数。这个方案数等于 前缀集合的总子集数 减去 和小于等于 \(a[i]\) 的子集方案数。
iii. \({a[0], ..., a[i-1]}\) 构成的子集总共有 \(2^i\) 种。
iv. 和小于等于 \(a[i]\) 的方案数就是 \(sum(dp[s])\) for \(s\) from 0 to \(a[i]\)。
v. 将 \((2^i - sum(dp[s]))\) 加入总答案 \(ans\)。这一步计算出的方案已自动满足“子集大小至少为2”的隐藏条件,因为和为0(空集)与和为\(a[j]\)(单个元素)都不可能大于\(a[i]\)。
vi. 将 \(a[i]\) “放入背包”,更新 \(dp\) 数组,为下一次迭代做准备。更新 \(dp\) 的方式是:\(for (s=...; s >= a[i]; --s) { dp[s] += dp[s - a[i]]; }\)。
d. 所有计算都在模 \(998,244,353\) 下进行。
AC 代码
#include <bits/stdc++.h>using namespace std;const int MOD = 998244353;void setup_io() {freopen("polygon.in", "r", stdin);freopen("polygon.out", "w", stdout);
}int main() {ios_base::sync_with_stdio(false);cin.tie(NULL);setup_io();int n;cin >> n;vector<int> a(n);for (int i = 0; i < n; ++i) {cin >> a[i];}sort(a.begin(), a.end());long long total_ans = 0;// dp[s]: 和为s的方案数。a[i]最大5000,我们只需统计和不大于5000的方案,所以数组大小开到5001。vector<long long> dp(5001, 0); dp[0] = 1;// 预计算2的幂,避免重复计算vector<long long> pow2(n + 1);pow2[0] = 1;for(int i = 1; i <= n; ++i) {pow2[i] = (pow2[i-1] * 2) % MOD;}long long current_prefix_sum = 0;for (int i = 0; i < n; ++i) {// Step 1: 以 a[i] 为最长边,从 a[0]...a[i-1] 中选边// 计算 sum(dp[s]) for s from 0 to a[i]long long subsets_le_ai = 0;for (int s = 0; s <= a[i]; ++s) {subsets_le_ai = (subsets_le_ai + dp[s]) % MOD;}// 总子集数是 2^ilong long total_subsets_prefix = pow2[i];// 和 > a[i] 的子集数long long good_subsets_count = (total_subsets_prefix - subsets_le_ai + MOD) % MOD;total_ans = (total_ans + good_subsets_count) % MOD;// Step 2: 更新 dp 数组,将 a[i] 加入选择池current_prefix_sum += a[i];for (int s = min((long long)5000, current_prefix_sum); s >= a[i]; --s) {dp[s] = (dp[s] + dp[s - a[i]]) % MOD;}}cout << total_ans << endl;return 0;
}