官方题解 | 巅峰赛#36
2026-07-20 09:51:06
发布于:浙江
巅峰赛36 题解
本次题目的总体难度如下,各位选手可以借此评估一下自身的技术水平
| 题目编号 | 题目标题 | 难度 |
|---|---|---|
| T1 | 午枫的传话游戏 | 普及/提高- |
| T2 | 午枫的教室路线 | 普及/提高- |
| T3 | 午枫的数字替换 | 普及/提高- |
| T4 | 午枫的课堂笔记 | 普及/提高- |
| T5 | 午枫的自习时间 | 普及+/提高 |
| T6 | 午枫的涂色方案 | 普及+/提高 |
T1 午枫的传话游戏
题意简述
有 个同学,每人有一个数字 。若两数绝对差为 则可直接传话,传话关系具有传递性。求最少添加多少对直接传话关系,使得整个图连通(任意两人可互相传话)。
解题思路
将数字视为节点,值相等的同学共享同一节点(他们之间差值为 ,需要额外处理)。先对数组排序去重分析连续段:如果两个相邻数值相差为 ,则它们天然有边相连;若相差 ,则这两段之间需要添加一条边来连通。因此,数值轴上会形成若干个由差值为 的连续段构成的连通块。
对于同一数值有多人的情况:这些人内部没有直接边(差值 ),他们必须借助相邻数值才能连通。如果一个连通块中只有一个数值(左右均断),那么该数值内的 个同学需要 条边才能内部连通,并且还需与外部块连接。当连通块有多个数值时,块内的所有同学可通过链式结构自然连通,只需向外连边时添加一条边。
边数最少为 。
参考代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) cin >> a[i];
sort(a.begin(), a.end());
int cnt = 0, l = 0, r = -1, t = 0;
for (int x : a) {
if (x > r + 1) {
cnt += (l == r) ? t : 1;
l = x;
t = 0;
}
r = x;
t++;
}
cnt += (l == r) ? t : 1;
cout << cnt - 2 << endl;
}
return 0;
}
T2 午枫的教室路线
题意简述
有一个 的网格,从 走到 ,每次只能向下或向右。给定一个长度为 的字符串 ,其中 D 表示这一步必须向下,R 表示必须向右,? 表示可以自由选择向下或向右。对于每一种合法的路径,我们把该路径经过的所有格子(包括起点和终点)标记为已访问。问在所有合法路径中,最多能有多少个不同的格子被至少一条路径访问到。
解题思路
令必须向下的步数为 ,必须向右的步数为 ,自由步中需选 步为向下、 步为向右。问题等价于求有多少格子 存在一种 ? 的赋值,使得路径经过该格。
考虑格子 ,到达它需要 步,其中向下 步。设前 步中必须向下 步、必须向右 步、自由 步。后 步中必须向下 步、自由 步。存在合法路径经过 当且仅当存在整数 (前 步中选择向下的自由步数)满足:
- ,即
- ,且
- ,即
消去变量后,固定 时 的取值范围为:
其中 ,。每个 对应格子 。
遍历 到 ,累加每个 对应的合法 的个数,即为答案。起点 和终点 自动包含在内。
参考代码
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
const int N = 400010;
int d[N], r[N], s[N];
string S;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
int h, w;
cin >> h >> w;
cin >> S;
S = " " + S;
int len = h + w - 2;
for (int i = 1; i <= len; i++) {
d[i] = d[i-1] + (S[i] == 'D');
r[i] = r[i-1] + (S[i] == 'R');
s[i] = s[i-1] + (S[i] == '?');
}
int total_d = d[len];
int total_r = r[len];
int n = h - 1 - total_d;
int m = w - 1 - total_r;
long long ans = 0;
for (int k = 0; k <= len; k++) {
int L = max(d[k] + 1, k - min(s[k], m) - r[k] + 1);
int R = min(min(s[k], n) + d[k] + 1, k - r[k] + 1);
if (L <= R) {
ans += R - L + 1;
}
}
cout << ans << '\n';
}
return 0;
}
T3 午枫的数字替换
题意简述
给定一个长度为 的数字串 和一个长度为 的数字串 ,按顺序进行 次操作,第 次操作可以将 的任意一个位置替换为 的第 个字符。每次替换会覆盖原有数字。问经过全部 次操作后,得到的 作为整数值最大是多少,输出这个字符串。
解题思路
我们依次进行 次替换,每次可以选任意位置。最后一次操作特别重要,因为它不会被后续操作覆盖。如果我们把某个数字放在最后一次操作中,它就会永远留在最终字符串的那个位置上。
为了使得最终字符串的字典序最大,我们希望前面的字符尽可能大。因此,一个直观的贪心策略是:除了最后一次操作的数字以外,前面的 次操作我们可以自由选择用哪些数字去替换哪些位置。我们把这 个数字从大到小排序,然后从左到右扫描 的每个位置,如果当前最大的可用数字比 这个位置上的数字大,就进行替换,然后这个数字被消耗掉,继续看下一个位置。这样可以保证在前面的位置上尽量使用大的数字。
接下来考虑最后一次操作的数字 。它必须被用到某个位置上。如果在前面的贪心过程中, 已经被当作一个较大的数字使用过了(即它已经被换到了某个位置上),那就不用再额外处理。如果没有被使用过,说明 比 中所有未被替换过的位置上的数字都小(因为贪心过程中只有遇到更大的数字才会替换)。此时,我们只能把它放在某个位置上,为了对整个字符串的字典序影响最小,应该把它放在最后一位。因为放在任何更前面的位置都会使得那个位置变小,从而让整个字符串变小,而放在末尾只会影响最后一位,前面的大数字结构保持不变。
这样操作后,得到的字符串就是最大的可能结果。
参考代码
#include <bits/stdc++.h>
using namespace std;
int n, m, it;
string s, t;
char last_char;
bool used;
int main() {
cin >> n >> m;
cin >> s >> t;
s = " " + s;
t = " " + t;
last_char = t[m];
sort(t.begin() + 1, t.end());
it = m;
for (int i = 1; i <= n; i++) {
if (it > 0 && t[it] > s[i]) {
s[i] = t[it];
it--;
}
}
used = false;
for (int i = 1; i <= n; i++) {
if (s[i] == last_char) {
used = true;
break;
}
}
if (!used) {
s[n] = last_char;
}
for (int i = 1; i <= n; i++) {
cout << s[i];
}
cout << endl;
return 0;
}
T4 午枫的课堂笔记
题意简述
按顺序处理 个数,对每个数必须恰好执行一次操作:要么写入到笔记板末尾,要么删除笔记板末尾的一个数(板为空时不能删除)。操作结束后,笔记板中剩余数的和即为结果。求最大可能和。
解题思路
定义 表示处理完前 个数且第 步执行删除操作时,当前栈中数字和的最大值; 表示第 步执行写入操作时的最大值。
若第 步是删除,则它必须删除上一步刚写入的数,因此第 步必须是写入,删除后状态回到第 步之后的状态,故 。
若第 步是写入,则直接加上 ,可以从第 步的任意状态转移来,即 。
初始条件 不合法(第一步不能删除),设为负无穷;。最终答案为 。
参考代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 2e5 + 5;
const ll INF = 0xc0c0c0c0c0c0c0c0;
ll n, a[MAXN];
ll f[MAXN][2];
int main() {
scanf("%lld", &n);
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
f[1][0] = INF;
f[1][1] = a[1];
for (int i = 2; i <= n; i++) {
f[i][0] = max(f[i-2][0], f[i-2][1]);
f[i][1] = max(f[i-1][0], f[i-1][1]) + a[i];
}
printf("%lld\n", max(f[n][0], f[n][1]));
return 0;
}
T5 午枫的自习时间
题意简述
给定一个长度为 的字符串 ,由 o 和 x 组成。将 重复 次得到字符串 (长度为 )。你可以将 中恰好 个 x 改为 o。问修改后最长的连续 o 子串长度最大是多少。
解题思路
设 中 x 的总数为 。由于 可能很大(),不能直接构造 。考虑分情况讨论。
首先,如果 或 ,可以直接构造出 (长度最多 ),然后用双指针(滑动窗口)求最多包含 个 x 的最长区间即可。
当 时,考虑在完整的若干个 块中,每块有 个 x。设 ,即可以完全填满的块数。分三种情况:
- 若 ,则所有 个块都可以完全填成
o,答案为 。 - 若 ,则前 个完整的块可以全部填满,剩下 次调整机会(注意这里 STD 中写的是
k = k % cnt + cnt,相当于先消耗掉 个整块,剩下的 可能小于 ,但为了充分利用,实际上是在两个块中寻找最长的区间,最后加上 作为中间已填满的块的长度)。 - 若 ,则中间有 个完整填满的块,剩下的 次调整机会用于在前后两个块中跨越拼接。同样用双指针在 上找最多包含 个
x的最长区间,然后加上 作为中间完整块的长度。
关键点在于:当 很大时,最优解一定由若干完整填满的 o 块加上两端部分块内扩展得到的连续区间组成。通过在 上运行滑动窗口(最多包含 或 个 x),可以求出跨越两个块边界时的最大长度,再加上中间完整块的总长度即可。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solve() {
ll n, m, k;
cin >> n >> m >> k;
string s;
cin >> s;
ll cnt = 0;
for (char c : s) {
if (c == 'x') cnt++;
}
if (m == 1) {
string t = " " + s;
int l = 1, r = 1, sum = 0, res = 0;
while (r <= n) {
while (r <= n) {
if (t[r] == 'x') sum++;
if (sum > k) break;
r++;
}
res = max(res, r - l);
while (l <= r) {
if (t[l] == 'x') sum--;
l++;
if (sum <= k) break;
}
r++;
}
cout << res << '\n';
return;
}
if (m == 2) {
string t = " " + s + s;
int l = 1, r = 1, sum = 0, res = 0;
while (r <= 2 * n) {
while (r <= 2 * n) {
if (t[r] == 'x') sum++;
if (sum > k) break;
r++;
}
res = max(res, r - l);
while (l <= r) {
if (t[l] == 'x') sum--;
l++;
if (sum <= k) break;
}
r++;
}
cout << res << '\n';
return;
}
ll num = k / cnt;
if (num >= m) {
cout << n * m << '\n';
return;
}
string t = " " + s + s;
ll res = 0;
if (num == m - 1) {
ll rem = k % cnt + cnt;
int l = 1, r = 1, sum = 0;
while (r <= 2 * n) {
while (r <= 2 * n) {
if (t[r] == 'x') sum++;
if (sum > rem) break;
r++;
}
res = max(res, (ll)(r - l));
while (l <= r) {
if (t[l] == 'x') sum--;
l++;
if (sum <= rem) break;
}
r++;
}
res += (num - 1) * n;
cout << res << '\n';
} else {
ll rem = k % cnt;
int l = 1, r = 1, sum = 0;
while (r <= 2 * n) {
while (r <= 2 * n) {
if (t[r] == 'x') sum++;
if (sum > rem) break;
r++;
}
res = max(res, (ll)(r - l));
while (l <= r) {
if (t[l] == 'x') sum--;
l++;
if (sum <= rem) break;
}
r++;
}
res += num * n;
cout << res << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}
T6 午枫的涂色方案
题意简述
一排 个座位,初始状态第 个座位上的数字为 ,即 交替。每次操作可以选择两个位置 和 满足 ,且 和 上的数字相同,而 内的所有数字都与 不同,然后将 内所有数字改为 上的数字。给定目标状态 ,问有多少种不同的操作序列(操作次数不同或某一步的 不同都算不同)能从初始状态变成目标状态,答案对 取模。
解题思路
首先无解条件: 必须为 (位置 永远不会被修改), 必须等于 (位置 也不会被修改)。否则答案为 。
将初始序列视为 个长度为 、颜色交替的连续段。一次操作选择三个连续段(左、中、右),其中左右颜色相同、中间相反,操作后三段的中间段变为左右颜色,三段合并为一段。因此操作只合并相邻的相同颜色段,且每次减少两个段。
最终目标 划分成若干连续同色段。每个段的长度必须为奇数(因为每次合并三个奇数长度段得到奇数长度段,从 开始只能得到奇数)。若任何一段长度为偶数,答案为 。
对于长度为 的段,它由 个初始段合并而成,需要 次操作。其内部合并方案数为 。
设最终有 个段,半长度分别为 (即段长为 ),总操作数 。不同段之间的操作可以任意交错,穿插方案数为多重组合数 。
因此总方案数为:
预处理阶乘、逆元及 即可 计算。
参考代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
const int P = 998244353;
long long f[N], fac[N], inv[N], fac_inv[N];
int a[N], n;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
if (a[1] != 1 || a[n] != (n % 2)) {
cout << 0 << endl;
return 0;
}
f[0] = f[1] = 1;
fac[0] = fac[1] = 1;
inv[1] = 1;
fac_inv[0] = fac_inv[1] = 1;
for (int i = 2; i <= n; i++) {
f[i] = f[i-1] * (2 * i - 1) % P;
fac[i] = fac[i-1] * i % P;
inv[i] = (P - P / i) * inv[P % i] % P;
fac_inv[i] = fac_inv[i-1] * inv[i] % P;
}
long long ans = 1;
int total_k = 0;
int len = 1;
for (int i = 2; i <= n + 1; i++) {
if (i <= n && a[i] == a[i-1]) {
len++;
} else {
if (len % 2 == 0) {
cout << 0 << endl;
return 0;
}
int k = len / 2;
total_k += k;
ans = ans * fac_inv[k] % P * f[k] % P;
len = 1;
}
}
ans = ans * fac[total_k] % P;
cout << ans << endl;
return 0;
}
全部评论 36




2026-07-22 来自 浙江
18
2026-07-26 来自 浙江
7

2026-07-22 来自 重庆
17依然NB
2026-07-22 来自 浙江
17a
2026-07-22 来自 河北
14

2026-07-23 来自 重庆
10
2026-07-22 来自 浙江
8互关互赞
2026-07-23 来自 北京
7厉害
2026-07-22 来自 浙江
71
2026-07-23 来自 浙江
6666
2026-07-22 来自 云南
5…
2026-07-22 来自 浙江
41
2026-07-22 来自 河北
4好
2026-07-22 来自 浙江
2T6哪有题解讲得这么难?这不是一行代码就能搞定的吗??

exec("import sys\nP = 998244353\ndata = sys.stdin.read().split()\nn = int(data[0])\na = [0] + [int(x) for x in data[1:n+1]]\nif a[1] != 1 or a[n] != (n % 2):\n print(0)\n sys.exit(0)\nf = [0] * (n+1)\nfac = [0] * (n+1)\ninv = [0] * (n+1)\nfac_inv = [0] * (n+1)\nf[0] = f[1] = 1\nfac[0] = fac[1] = 1\ninv[1] = 1\nfac_inv[0] = fac_inv[1] = 1\nfor i in range(2, n+1):\n f[i] = f[i-1] * (2*i - 1) % P\n fac[i] = fac[i-1] * i % P\n inv[i] = (P - P // i) * inv[P % i] % P\n fac_inv[i] = fac_inv[i-1] * inv[i] % P\ntotal_k = 0\nans = 1\nlength = 1\nfor i in range(2, n+2):\n if i <= n and a[i] == a[i-1]:\n length += 1\n else:\n if length % 2 == 0:\n print(0)\n sys.exit(0)\n k = length // 2\n total_k += k\n ans = ans * fac_inv[k] % P * f[k] % P\n length = 1\nans = ans * fac[total_k] % P\nprint(ans)\n")2026-07-31 来自 浙江
1d
2026-07-31 来自 浙江
0d
2026-07-31 来自 浙江
0
6
2026-07-29 来自 浙江
16
2026-07-29 来自 浙江
16
2026-07-29 来自 浙江
1互关


2026-07-29 来自 北京
1
2026-07-28 来自 浙江
11
2026-07-27 来自 广东
1




























































有帮助,赞一个