【C++】2025CSP-J第二轮真题及解析
P14357 [CSP-J 2025] 拼数 / number(民间数据)
题目描述
小 R 正在学习字符串处理。小 X 给了小 R 一个字符串 sss,其中 sss 仅包含小写英文字母及数字,且包含至少一个 1∼91 \sim 91∼9 中的数字。小 X 希望小 R 使用 sss 中的任意多个数字,按任意顺序拼成一个正整数。注意:小 R 可以选择 sss 中相同的数字,但每个数字只能使用一次。例如,若 sss 为 1a01b\tt 1a01b1a01b,则小 R 可以同时选择第 1,3,41,3,41,3,4 个字符,分别为 1,0,11,0,11,0,1,拼成正整数 101101101 或 110110110;但小 R 不能拼成正整数 111111111,因为 sss 仅包含两个数字 111。小 R 想知道,在他所有能拼成的正整数中,最大的是多少。你需要帮助小 R 求出他能拼成的正整数的最大值。
输入格式
输入的第一行包含一个字符串 sss,表示小 X 给小 R 的字符串。
输出格式
输出一行一个正整数,表示小 R 能拼成的正整数的最大值。
输入输出样例 #1
输入 #1
5
输出 #1
5
输入输出样例 #2
输入 #2
290es1q0
输出 #2
92100
说明/提示
【样例 2 解释】
sss 包含数字 2,9,0,1,02,9,0,1,02,9,0,1,0。可以证明,小 R 拼成的正整数的最大值为 921009210092100。
【样例 3】
见选手目录下的 number/number3.innumber/number3.innumber/number3.in 与 number/number3.ansnumber/number3.ansnumber/number3.ans。该样例满足测试点 9∼119 \sim 119∼11 的约束条件。
【样例 4】
见选手目录下的 number/number4.innumber/number4.innumber/number4.in 与 number/number4.ansnumber/number4.ansnumber/number4.ans。该样例满足测试点 202020 的约束条件。
【数据范围】
设∣s∣|s|∣s∣为字符串sss的长度。对于所有测试数据,保证:
- 1≤∣s∣≤1061 \leq |s| \leq 10^61≤∣s∣≤106;
- sss仅包含小写英文字母及数字,且包含至少一个1∼91 \sim 91∼9中的数字。
| 测试点编号 | s≤s \leqs≤ | 特殊性质 |
|---|---|---|
| 111 | 111 | A |
| 222 | 222 | 无 |
| 333 | 222 | 无 |
| 444 | 101010 | A |
| 5,65, 65,6 | 101010 | 无 |
| 7,87, 87,8 | 10210^2102 | A |
| 9∼119 \sim 119∼11 | 10210^2102 | 无 |
| 121212 | 10310^3103 | A |
| 13,1413, 1413,14 | 10310^3103 | 无 |
| 151515 | 10510^5105 | A |
| 16,1716, 1716,17 | 10510^5105 | B |
| 18,1918, 1918,19 | 10510^5105 | 无 |
| 202020 | 10610^6106 | A |
| 21,2221, 2221,22 | 10610^6106 | B |
| 23∼2523 \sim 2523∼25 | 10610^6106 | 无 |
其中,特殊性质的说明如下:
- 特殊性质A:sss仅包含数字;
- 特殊性质B:sss仅包含不超过10310^3103个数字。
算法分析
这道题比较简单,用到桶排序算法。读入时,用string,记录每个0~9的数字,输出时反向输出即可。
完整代码
#include <bits/stdc++.h>
using namespace std;int MAX = 1e6 + 10;string S;
int a[10];int main() {cin >> S;int size = S.size();for (int i = 0; i < size; i++) {int n = S[i] - '0';if (n >= 0 && n <= 9)a[n]++; // 桶}for (int i = 9; i >= 0; i--) {while (a[i] != 0) {a[i]--;cout << i;}}return 0;
}
P14358 [CSP-J 2025] 座位 / seat(民间数据)
题目描述
CSP-J 2025 第二轮正在进行。小 R 所在的考场共有 n×mn \times mn×m 名考生,其中所有考生的 CSP-J 2025 第一轮成绩互不相同。所有 n×mn \times mn×m 名考生将按照 CSP-J 2025 第一轮的成绩,由高到低蛇形分配座位,排列成 nnn 行 mmm 列。具体地,设小 R 所在的考场的所有考生的成绩从高到低分别为 s1>s2>⋯>sn×ms_1 > s_2 > \dots > s_{n \times m}s1>s2>⋯>sn×m,则成绩为 s1s_1s1 的考生的座位为第 1 列第 111 行,成绩为 s2s_2s2 的考生的座位为第 111 列第 222 行,…\dots…,成绩为 sns_nsn 的考生的座位为第 111 列第 nnn 行,成绩为 sn+1s_{n+1}sn+1 的考生的座位为第 222 列第 nnn 行,…\dots…,成绩为 s2ns_{2n}s2n 的考生的座位为第 222 列第 111 行,成绩为 s2n+1s_{2n+1}s2n+1 的考生的座位为第 333 列第 111 行,以此类推。
例如,若 n=4,m=5n = 4, m = 5n=4,m=5,则所有 4×5=204 \times 5 = 204×5=20 名考生将按照 CSP-J 2025 第一轮成绩从高到低的顺序,根据下图中的箭头顺序分配座位。

给定小 R 所在的考场座位的行数 nnn 与列数 mmm,以及小 R 所在的考场的所有考生 CSP-J 2025 第一轮的成绩 a1,a2,…,an×ma_1, a_2, \dots, a_{n \times m}a1,a2,…,an×m,其中 a1a_1a1 为小 R CSP-J 2025 第一轮的成绩,你需要帮助小 R 求出,他的座位为第几列第几行。
输入格式
输入的第一行包含两个正整数 n,mn, mn,m,分别表示小 R 所在的考场座位的行数与列数。
输入的第二行包含 n×mn \times mn×m 个正整数 a1,a2,…,an×ma_1, a_2, \dots, a_{n \times m}a1,a2,…,an×m,分别表示小 R 所在的考场的所有考生 CSP-J 2025 第一轮的成绩,其中 a1a_1a1 为小 R CSP-J 2025 第一轮的成绩。
输出格式
输出一行两个正整数 c,rc, rc,r,表示小 R 的座位为第 ccc 列第 rrr 行。
输入输出样例 #1
输入 #1
2 2
99 100 97 98
输出 #1
1 2
输入输出样例 #2
输入 #2
2 2
98 99 100 97
输出 #2
2 2
输入输出样例 #3
输入 #3
3 3
94 95 96 97 98 99 100 93 92
输出 #3
3 1
说明/提示
【样例 1 解释】
按照成绩从高到低的顺序,成绩为 100100100 的考生的座位为第 111 列第 111 行,成绩为 999999 的考生的座位为第 111 列第 222 行,成绩为 989898 的考生的座位为第 222 列第 222 行,成绩为 979797 的考生的座位为第 222 列第 111 行。小 R 的成绩为 999999,因此座位为第 111 列第 222 行。
【样例 2 解释】
按照成绩从高到低的顺序,成绩为 100100100 的考生的座位为第 111 列第 111 行,成绩为 999999 的考生的座位为第 111 列第 222 行,成绩为 989898 的考生的座位为第 222 列第 222 行,成绩为 979797 的考生的座位为第 222 列第 111 行。小 R 的成绩为 989898,因此座位为第 222 列第 222 行。
【数据范围】
对于所有测试数据,保证:
- 1≤n≤101 \leq n \leq 101≤n≤10, 1≤m≤101 \leq m \leq 101≤m≤10;
- 对于所有 1≤i≤n×m1 \leq i \leq n \times m1≤i≤n×m,均有 1≤ai≤1001 \leq a_i \leq 1001≤ai≤100,且 a1,a2,…,an×ma_1, a_2, \dots, a_{n \times m}a1,a2,…,an×m 互不相同。
| 测试点编号 | n≤n \leqn≤ | m≤m \leqm≤ | 特殊性质 |
|---|---|---|---|
| 111 | 111 | 111 | AB |
| 2,32, 32,3 | 111 | 101010 | 无 |
| 4,54, 54,5 | 101010 | 111 | 无 |
| 666 | 222 | 222 | A |
| 777 | 222 | 222 | B |
| 8,98, 98,9 | 222 | 222 | 无 |
| 101010 | 222 | 101010 | A |
| 111111 | 222 | 101010 | B |
| 12∼1412 \sim 1412∼14 | 222 | 101010 | 无 |
| 15∼1715 \sim 1715∼17 | 101010 | 222 | 无 |
| 18∼2018 \sim 2018∼20 | 101010 | 101010 | 无 |
特殊性质 A:对于所有 1≤i≤n×m1 \leq i \leq n \times m1≤i≤n×m,均有 ai=ia_i = iai=i。
特殊性质 B:对于所有 1≤i≤n×m1 \leq i \leq n \times m1≤i≤n×m,均有 ai=n×m−i+1a_i = n \times m - i + 1ai=n×m−i+1。
算法分析
这题也比较简单(轻轻松松200分~)涉及到的算法为模拟算法。
用sort对分数进行排序后,根据给定的题意进行for循环嵌套即可。
完整代码
#include <bits/stdc++.h>
using namespace std;#define MAXN 1505
int n, m;
int a[MAXN];// 从大到小
bool cmp(int a, int b) { return a > b; }int main() {cin >> n >> m;for (int i = 1; i <= n * m; i++) {cin >> a[i];}const int r = a[1];sort(a + 1, a + n * m + 1, cmp);// for (int i = 1; i <= n * m; i++) {// cout << a[i] << " ";// }// cout << endl;int idx = 1;for (int j = 1; j <= m; j++) {if (j % 2 != 0) {for (int i = 1; i <= n; i++) {if (a[idx] == r) {cout << j << " " << i;}idx++;}} else {for (int i = n; i >= 1; i--) {if (a[idx] == r) {cout << j << " " << i;}idx++;}}}return 0;
}
P14359 [CSP-J 2025] 异或和 / xor(民间数据)
题目描述
小 R 有一个长度为 nnn 的非负整数序列 a1,a2,…,ana_1, a_2, \dots, a_na1,a2,…,an。定义一个区间 [l,r][l, r][l,r] (1≤l≤r≤n1 \leq l \leq r \leq n1≤l≤r≤n) 的权值为 al,al+1,…,ara_l, a_{l+1}, \dots, a_ral,al+1,…,ar 的二进制按位异或和,即 al⊕al+1⊕⋯⊕ara_l \oplus a_{l+1} \oplus \dots \oplus a_ral⊕al+1⊕⋯⊕ar,其中 ⊕\oplus⊕ 表示二进制按位异或。
小 X 给了小 R 一个非负整数 kkk。小 X 希望小 R 选择序列中尽可能多的不相交的区间,使得每个区间的权值均为 kkk。两个区间 [l1,r1],[l2,r2][l_1, r_1], [l_2, r_2][l1,r1],[l2,r2] 相交当且仅当两个区间同时包含至少一个相同的下标,即存在 1≤i≤n1 \leq i \leq n1≤i≤n 使得 l1≤i≤r1l_1 \leq i \leq r_1l1≤i≤r1 且 l2≤i≤r2l_2 \leq i \leq r_2l2≤i≤r2。
例如,对于序列 [2,1,0,3][2, 1, 0, 3][2,1,0,3],若 k=2k = 2k=2,则小 R 可以选择区间 [1,1][1, 1][1,1] 和区间 [2,4][2, 4][2,4],权值分别为 222 和 1⊕0⊕3=21 \oplus 0 \oplus 3 = 21⊕0⊕3=2;若 k=3k = 3k=3,则小 R 可以选择区间 [1,2][1, 2][1,2] 和区间 [4,4][4, 4][4,4],权值分别为 1⊕2=31 \oplus 2 = 31⊕2=3 和 333。
你需要帮助小 R 求出他能选出的区间数量的最大值。
输入格式
输入的第一行包含两个非负整数 n,kn, kn,k,分别表示小 R 的序列长度和小 X 给小 R 的非负整数。
输入的第二行包含 nnn 个非负整数 a1,a2,…,ana_1, a_2, \dots, a_na1,a2,…,an,表示小 R 的序列。
输出格式
输出一行一个非负整数,表示小 R 能选出的区间数量的最大值。
输入输出样例 #1
输入 #1
4 2
2 1 0 3
输出 #1
2
输入输出样例 #2
输入 #2
4 3
2 1 0 3
输出 #2
2
输入输出样例 #3
输入 #3
4 0
2 1 0 3
输出 #3
1
说明/提示
【样例 1 解释】
小 R 可以选择区间 [1,1][1, 1][1,1] 和区间 [2,4][2, 4][2,4],异或和分别为 222 和 1⊕0⊕3=21 \oplus 0 \oplus 3 = 21⊕0⊕3=2。可以证明,小 R 能选出的区间数量的最大值为 222。
【样例 2 解释】
小 R 可以选择区间 [1,2][1, 2][1,2] 和区间 [4,4][4, 4][4,4],异或和分别为 1⊕2=31 \oplus 2 = 31⊕2=3 和 333。可以证明,小 R 能选出的区间数量的最大值为 222。
【样例 3 解释】
小 R 可以选择区间 [3,3][3, 3][3,3],异或和为 000。可以证明,小 R 能选出的区间数量的最大值为 111。注意:小 R 不能同时选择区间 [3,3][3, 3][3,3] 和区间 [1,4][1, 4][1,4],因为这两个区间同时包含下标 333。
【样例 4】
见选手目录下的 xor/xor4.inxor/xor4.inxor/xor4.in 与 xor/xor4.ansxor/xor4.ansxor/xor4.ans。
该样例满足测试点 4,54, 54,5 的约束条件。
【样例 5】
见选手目录下的 xor/xor5.inxor/xor5.inxor/xor5.in 与 xor/xor5.ansxor/xor5.ansxor/xor5.ans。
该样例满足测试点 9,109, 109,10 的约束条件。
【样例 6】
见选手目录下的 xor/xor6.inxor/xor6.inxor/xor6.in 与 xor/xor6.ansxor/xor6.ansxor/xor6.ans。
该样例满足测试点 14,1514, 1514,15 的约束条件。
【数据范围】
对于所有测试数据,保证:
- 1≤n≤5×1051 \leq n \leq 5 \times 10^51≤n≤5×105,0≤k<2200 \leq k < 2^{20}0≤k<220;
- 对于所有 1≤i≤n1 \leq i \leq n1≤i≤n,均有 0≤ai<2200 \leq a_i < 2^{20}0≤ai<220。
| 测试点编号 | n≤n \leqn≤ | kkk 范围 | 特殊性质 |
|---|---|---|---|
| 111 | 222 | =0= 0=0 | A |
| 222 | 101010 | ≤1\leq 1≤1 | B |
| 333 | 10210^2102 | =0= 0=0 | A |
| 4,54, 54,5 | 10210^2102 | ≤1\leq 1≤1 | B |
| 6∼86 \sim 86∼8 | 10210^2102 | ≤255\leq 255≤255 | C |
| 9,109, 109,10 | 10310^3103 | <220< 2^{20}<220 | 无 |
| 11,1211, 1211,12 | 10310^3103 | <220< 2^{20}<220 | 无 |
| 131313 | 2×1052 \times 10^52×105 | ≤1\leq 1≤1 | B |
| 14,1514, 1514,15 | 2×1052 \times 10^52×105 | ≤255\leq 255≤255 | C |
| 161616 | 2×1052 \times 10^52×105 | <220< 2^{20}<220 | 无 |
| 171717 | 5×1055 \times 10^55×105 | ≤255\leq 255≤255 | C |
| 18∼2018 \sim 2018∼20 | 5×1055 \times 10^55×105 | <220< 2^{20}<220 | 无 |
其中,特殊性质的说明如下:
- 特殊性质A:对于所有 1≤i≤n1 \leq i \leq n1≤i≤n,均有 ai=1a_i = 1ai=1;
- 特殊性质B:对于所有 1≤i≤n1 \leq i \leq n1≤i≤n,均有 0≤ai≤10 \leq a_i \leq 10≤ai≤1;
- 特殊性质C:对于所有 1≤i≤n1 \leq i \leq n1≤i≤n,均有 0≤ai≤2550 \leq a_i \leq 2550≤ai≤255。
算法分析
- 求异或的代码为
a=b^c,如果这个忘了就基本凉凉了~ - 这题用到的算法是dp,定义
dp[i]表示前iii个元素中能选出的最大不相交区间数量。 - 对于每个位置iii,有两种选择:
- 不选第 iii个元素作为区间终点:此时
dp[i] = dp[i-1] - 选第iii个元素作为区间终点:需找到以iii结尾且异或和为kkk的区间 [j+1,i][j+1, i][j+1,i],则
dp[i] = max(dp[i], dp[j] + 1)
- 不选第 iii个元素作为区间终点:此时
- 至此,算法逻辑写完可以拿60分,接下来是优化。
- 在反向查找时添加一个
break,因为找到一个区间,后面的区间长度不会超过当前的区间长度。(这个可以拿80分) - 记录上一个区间的结束位置,定义一个
last变量记录上一个选中区间的结束位置,确保区间不相交,只需要检查j >= last的范围。(至此,已拿满分)
- 在反向查找时添加一个
完整代码
#include <bits/stdc++.h>
using namespace std;#define int long long
const int MAXN = 1e5 * 5 + 10;int n, k;
int a[MAXN], dp[MAXN];signed main() {cin >> n >> k;for (int i = 1; i <= n; i++) {cin >> a[i];}dp[0] = 0;int last = 0; // 记录上一个区间的结束位置for (int i = 1; i <= n; i++) {dp[i] = dp[i - 1];int s = a[i];for (int j = i - 1; j >= last; j--) {if (s == k) { // 找到可能的区间dp[i] = max(dp[i], dp[j] + 1);last = j; // 更新上一个区间的结束位置break; // 找到一个区间,后面的区间长度不会超过当前的区间长度}s = s ^ a[j]; // 求异或和}}cout << dp[n] << endl;return 0;
}
P14360 [CSP-J 2025] 多边形 / polygon(民间数据)
题目描述
小 R 喜欢玩小木棍。小 R 有 nnn 根小木棍,第 iii (1≤i≤n1 \leq i \leq n1≤i≤n) 根小木棍的长度为 aia_iai。
小 X 希望小 R 从这 nnn 根小木棍中选出若干根小木棍,将它们按任意顺序首尾相连拼成一个多边形。小 R 并不知道小木棍能拼成多边形的条件,于是小 X 直接将条件告诉了他:对于长度分别为 l1,l2,…,lml_1, l_2, \dots, l_ml1,l2,…,lm 的 mmm 根小木棍,这 mmm 根小木棍能拼成一个多边形当且仅当 m≥3m \geq 3m≥3 且所有小木棍的长度之和大于所有小木棍的长度最大值的两倍,即 ∑i=1mli>2×maxi=1mli\sum_{i=1}^{m} l_i > 2 \times \max_{i=1}^{m} l_i∑i=1mli>2×maxi=1mli。
由于小 R 知道了小木棍能拼成多边形的条件,小 X 提出了一个更难的问题:有多少种选择小木棍的方案,使得选出的小木棍能够拼成一个多边形?你需要帮助小 R 求出选出的小木棍能够拼成一个多边形的方案数。两种方案不同当且仅当选择的小木棍的下标集合不同,即存在 1≤i≤n1 \leq i \leq n1≤i≤n,使得其中一种方案选择了第 iii 根小木棍,但另一种方案未选择。由于答案可能较大,你只需要求出答案对 998,244,353998,244,353998,244,353 取模后的结果。
输入格式
输入的第一行包含一个正整数 nnn,表示小 R 的小木棍的数量。
输入的第二行包含 nnn 个正整数 a1,a2,…,ana_1, a_2, \dots, a_na1,a2,…,an,表示小 R 的小木棍的长度。
输出格式
输出一行一个非负整数,表示小 R 选出的小木棍能够拼成一个多边形的方案数对 998,244,353998,244,353998,244,353 取模后的结果。
输入输出样例 #1
输入 #1
5
1 2 3 4 5
输出 #1
9
输入输出样例 #2
输入 #2
5
2 2 3 8 10
输出 #2
6
说明/提示
【样例 1 解释】
共有以下 999 种选择小木棍的方案,使得选出的小木棍能够拼成一个多边形:
- 选择第 2,3,42, 3, 42,3,4 根小木棍,长度之和为 2+3+4=92 + 3 + 4 = 92+3+4=9,长度最大值为 444;
- 选择第 2,4,52, 4, 52,4,5 根小木棍,长度之和为 2+4+5=112 + 4 + 5 = 112+4+5=11,长度最大值为 555;
- 选择第 3,4,53, 4, 53,4,5 根小木棍,长度之和为 3+4+5=123 + 4 + 5 = 123+4+5=12,长度最大值为 555;
- 选择第 1,2,3,41, 2, 3, 41,2,3,4 根小木棍,长度之和为 1+2+3+4=101 + 2 + 3 + 4 = 101+2+3+4=10,长度最大值为 444;
- 选择第 1,2,3,51, 2, 3, 51,2,3,5 根小木棍,长度之和为 1+2+3+5=111 + 2 + 3 + 5 = 111+2+3+5=11,长度最大值为 555;
- 选择第 1,2,4,51, 2, 4, 51,2,4,5 根小木棍,长度之和为 1+2+4+5=121 + 2 + 4 + 5 = 121+2+4+5=12,长度最大值为 555;
- 选择第 1,3,4,51, 3, 4, 51,3,4,5 根小木棍,长度之和为 1+3+4+5=131 + 3 + 4 + 5 = 131+3+4+5=13,长度最大值为 555;
- 选择第 2,3,4,52, 3, 4, 52,3,4,5 根小木棍,长度之和为 2+3+4+5=142 + 3 + 4 + 5 = 142+3+4+5=14,长度最大值为 555;
- 选择第 1,2,3,4,51, 2, 3, 4, 51,2,3,4,5 根小木棍,长度之和为 1+2+3+4+5=151 + 2 + 3 + 4 + 5 = 151+2+3+4+5=15,长度最大值为 555。
【样例 2 解释】
共有以下 666 种选择小木棍的方案,使得选出的小木棍能够拼成一个多边形:
- 选择第 1,2,31, 2, 31,2,3 根小木棍,长度之和为 2+2+3=72 + 2 + 3 = 72+2+3=7,长度最大值为 333;
- 选择第 3,4,53, 4, 53,4,5 根小木棍,长度之和为 3+8+10=213 + 8 + 10 = 213+8+10=21,长度最大值为 101010;
- 选择第 1,2,4,51, 2, 4, 51,2,4,5 根小木棍,长度之和为 2+2+8+10=222 + 2 + 8 + 10 = 222+2+8+10=22,长度最大值为 101010;
- 选择第 1,3,4,51, 3, 4, 51,3,4,5 根小木棍,长度之和为 2+3+8+10=232 + 3 + 8 + 10 = 232+3+8+10=23,长度最大值为 101010;
- 选择第 2,3,4,52, 3, 4, 52,3,4,5 根小木棍,长度之和为 2+3+8+10=232 + 3 + 8 + 10 = 232+3+8+10=23,长度最大值为 101010;
- 选择第 1,2,3,4,51, 2, 3, 4, 51,2,3,4,5 根小木棍,长度之和为 2+2+3+8+10=252 + 2 + 3 + 8 + 10 = 252+2+3+8+10=25,长度最大值为 101010。
【样例 3】
见选手目录下的 polygon/polygon3.inpolygon/polygon3.inpolygon/polygon3.in 与 polygon/polygon3.anspolygon/polygon3.anspolygon/polygon3.ans。
该样例满足测试点 7∼107 \sim 107∼10 的约束条件。
【样例 4】
见选手目录下的 polygon/polygon4.inpolygon/polygon4.inpolygon/polygon4.in 与 polygon/polygon4.anspolygon/polygon4.anspolygon/polygon4.ans。
该样例满足测试点 11∼1411 \sim 1411∼14 的约束条件。
【子任务】
对于所有测试数据,保证:
- 3≤n≤50003 \leq n \leq 50003≤n≤5000;
- 对于所有 1≤i≤n1 \leq i \leq n1≤i≤n,均有 1≤ai≤50001 \leq a_i \leq 50001≤ai≤5000。
| 测试点编号 | n≤n \leqn≤ | maxi=1nai≤\max_{i=1}^{n} a_i \leqmaxi=1nai≤ |
|---|---|---|
| 1∼31 \sim 31∼3 | 333 | 101010 |
| 4∼64 \sim 64∼6 | 101010 | 10210^2102 |
| 7∼107 \sim 107∼10 | 202020 | 10210^2102 |
| 11∼1411 \sim 1411∼14 | 500500500 | 10210^2102 |
| 15∼1715 \sim 1715∼17 | 500500500 | 111 |
| 18∼2018 \sim 2018∼20 | 500050005000 | 111 |
| 21∼2521 \sim 2521∼25 | 500050005000 | 500050005000 |
算法分析
- 观察后发现,总方案数好求(就是2n2^n2n),然后我们求得不满足条件的方案数,拿总方案数去减更方便。本题采用动态规划算法。
- 定义一个三维数组
dp,其中三个维度分别为:- 第一个维度
i:表示考虑前i个数,即处理到数组的第i个元素。 - 第二个维度
j:表示当前选择的元素的总和为j。 - 第三个维度
k(取值为0、1、2):表示在考虑前i个数且总和为j的情况下,当前选择的元素个数状态:k=0:表示一个元素都没有选。k=1:表示恰好选择了1个元素。k=2:表示选择了2个或2个以上的元素。
- 第一个维度
- 状态转移:
- 不选当前元素:继承上一层状态。
- 选当前元素:根据已有选中数量更新状态
- 当选择当前元素且满足
j >= a[i](即当前总和j能容纳该元素值)时:dp[i][j][1]的更新:在之前未选中任何元素(k=0)的状态下,选择当前元素后,选中数量变为1(k=1),因此加上dp[i-1][j - a[i]][0]。dp[i][j][2]的更新:在之前已选中1个元素(k=1)或已选中2个及以上元素(k=2)的状态下,选择当前元素后,选中数量变为2个及以上(k=2),因此加上dp[i-1][j - a[i]][1] + dp[i-1][j - a[i]][2]。
- 代码如下所示。
- 定义一个三维数组
if (j >= a[i]) {dp[i][j][1] = (dp[i][j][1] + dp[i - 1][j - a[i]][0]) % MOD;dp[i][j][2] = (1ll * dp[i][j][2] + dp[i - 1][j - a[i]][1] + dp[i - 1][j - a[i]][2]) % MOD;
}
- 前缀和优化:用
sum[cur][j]记录前j个和的状态2的累加和,快速计算不满足条件的子集数量。
- 在处理第
i个元素、总和为j的状态时,通过前缀和sum[i][j]累加前j个总和中状态为k=2(即选择了2个及以上元素)的方案数。sum[i][j]的值由前一项sum[i][j-1]加上当前的dp[i][j][2]得到,实现了对状态2的累加和记录,从而可以快速计算相关的子集数量。 - 前缀和优化中用于记录前
j个和的状态2累加和的代码如下所示。
// 维护sum
sum[i][j] = (sum[i][j - 1] + dp[i][j][2]) % MOD;
- 结果计算:
- 对每个元素
a[i](排序后作为最大值),总方案数为2i−12^{i-1}2i−1(前i个元素的非空子集) - 减去无效方案即为答案。无效方案包括:大小为1的子集、大小≥3但不满足和的条件的子集。
- 代码逻辑说明如下
mi[i - 1]:对应“总方案数为2i−12^{i-1}2i−1”,mi数组提前存储了2的幂次(mi[k] = 2^k mod MOD),前i个元素的非空子集数为2i−12^{i-1}2i−1。- i:对应“减去大小为1的子集”,前i个元素中大小为1的子集共i个(每个元素单独作为子集)。- sum[i - 1][a[i]]:对应“减去大小≥3但不满足和的条件的子集”,sum[i-1][a[i]]是前i-1个元素中“和≤a[i]且选中2个及以上元素”的方案数(这些方案加上a[i]后,总和≤2×a[i],不满足多边形条件)。- 整体累加并取模:将每个以
a[i]为最大值的有效方案数累加,得到最终答案。
- 对每个元素
int ans = 0;
for (int i = 3; i <= n; i++) {ans = ((ans + mi[i - 1] - i - sum[i - 1][a[i]]) % MOD + MOD) % MOD;
}
- 用滚动数组优化,不然会MLE只有68分。
dp数组的第一个维度用一个变量cur来滚动使用。dp[cur][j][s],其中cur为滚动数组当前层,j为元素和,s为状态。
完整代码
没有用滚动数组优化的原始版本:
#include <bits/stdc++.h>
using namespace std;#define int long long
const int MAXN = 5001;
const int MOD = 998244353;
int n, aMax;
// 前i个数里总和为j的方案数,k为选择的个数,k=2为选了2个以上
int a[MAXN], dp[MAXN][MAXN][3], sum[MAXN][MAXN];
int mi[MAXN];signed main() {cin >> n;for (int i = 1; i <= n; i++) {cin >> a[i];aMax = max(aMax, a[i]);}sort(a + 1, a + n + 1);// 初始化2的幂mi[0] = 1;for (int i = 1; i <= MAXN; i++) {mi[i] = mi[i - 1] * 2 % MOD;}dp[0][0][0] = 1;for (int i = 1; i <= n; i++) {dp[i][0][0] = 1;for (int j = 1; j <= aMax; j++) {// 不选dp[i][j][1] = dp[i - 1][j][1];dp[i][j][2] = dp[i - 1][j][2];// 选if (j >= a[i]) {dp[i][j][1] = (dp[i][j][1] + dp[i - 1][j - a[i]][0]) % MOD;dp[i][j][2] = (1ll * dp[i][j][2] + dp[i - 1][j - a[i]][1] + dp[i - 1][j - a[i]][2]) % MOD;}// 维护sumsum[i][j] = (sum[i][j - 1] + dp[i][j][2]) % MOD;}}int ans = 0;for (int i = 3; i <= n; i++) {ans = ((ans + mi[i - 1] - i - sum[i - 1][a[i]]) % MOD + MOD) % MOD;}cout << ans << endl;return 0;
}
用滚动数组优化后的版本如下。
#include <bits/stdc++.h>
using namespace std;#define int long long
const int MAXN = 5001;
const int MOD = 998244353;
int n, aMax;
int a[MAXN];
int dp[2][MAXN][3]; // 使用滚动数组,只保留当前层和上一层
int sum[2][MAXN]; // 滚动数组优化sum
int mi[MAXN];signed main() {cin >> n;for (int i = 1; i <= n; i++) {cin >> a[i];aMax = max(aMax, a[i]);}sort(a + 1, a + n + 1);// 初始化2的幂mi[0] = 1;for (int i = 1; i <= MAXN; i++) {mi[i] = mi[i - 1] * 2 % MOD;}// 初始化dp数组int cur = 0;memset(dp[cur], 0, sizeof(dp[cur]));dp[cur][0][0] = 1;// 初始化sum数组memset(sum, 0, sizeof(sum));int ans = 0;for (int i = 1; i <= n; i++) {cur ^= 1; // 切换当前层// 先清空当前层,然后复制不选的情况memset(dp[cur], 0, sizeof(dp[cur]));dp[cur][0][0] = 1; // 不选任何元素的情况// 复制上一层的状态作为不选当前元素的情况for (int j = 0; j <= aMax; j++) {dp[cur][j][1] = dp[cur ^ 1][j][1];dp[cur][j][2] = dp[cur ^ 1][j][2];}// 处理选当前元素的情况for (int j = a[i]; j <= aMax; j++) {// 选当前元素,且这是第一个选中的元素dp[cur][j][1] = (dp[cur][j][1] + dp[cur ^ 1][j - a[i]][0]) % MOD;// 选当前元素,且这是第二个或更多选中的元素dp[cur][j][2] = (dp[cur][j][2] + dp[cur ^ 1][j - a[i]][1] + dp[cur ^ 1][j - a[i]][2]) % MOD;}// 维护前缀和sum[cur][j] = sum_{k=0}^j dp[cur][k][2]sum[cur][0] = 0;for (int j = 1; j <= aMax; j++) {sum[cur][j] = (sum[cur][j - 1] + dp[cur][j][2]) % MOD;}// 计算以a[i]为最大值时的有效方案数// 注意:这里使用current层,对应原代码中的i层if (i >= 3) {// 总共有mi[i-1]种方式选择前i个元素中的非空子集// 减去只选一个元素的i种方式// 减去选三个或更多元素但不满足条件的情况(即sum[cur][a[i]])ans = ((ans + mi[i-1] - i - sum[cur][a[i]]) % MOD + MOD) % MOD;}}cout << ans << endl;return 0;
}