官方题解 | 巅峰赛30
2026-02-04 17:36:32
发布于:浙江
巅峰赛30题解
本次题目的总体难度如下,各位选手可以借此评估一下自身的技术水平
| 题目编号 | 题目标题 | 难度 |
|---|---|---|
| T1 | 雾港城的开关 | 普及 - |
| T2 | 雾港学宫的时钟塔调度 | 普及 - |
| T3 | 雾港学宫的能量点对 | 普及/提高- |
| T4 | 雾港城的道路网 | 普及/提高- |
| T5 | 雾港学宫的能量链 | 普及/提高- |
| T6 | 雾港城的服务中心 | 普及/提高- |
T1
简要题解
从左到右扫一遍,记 rev 表示前面做过的后缀翻转次数的奇偶性。当前位置的实际值是 (s[i]-'0') XOR rev;如果这一位实际为 1,就说明它只能通过在当前位置再翻一次才能归零(再往右的操作不会影响它),因此必须在 i 操作一次,答案加一并把 rev 取反。扫完整个串得到的计数就是最少操作次数。
参考代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
string s;
cin >> n >> s;
int rev = 0;
long long ans = 0;
for (int i = 0; i < n; i++) {
int bit = (s[i] - '0') ^ rev;
if (bit == 1) {
ans++;
rev ^= 1;
}
}
cout << ans << "\n";
return 0;
}
T2
简要题解
从左到右处理,维护前一个位置最终确定的值 need。为了严格递增,当前位置最终值至少要是 need+1;如果原来的 a[i] 已经不小于 need+1 就不用动,直接把 need 更新成 a[i],否则就把 a[i] 增加到 need+1,增加量累加进答案,并把 need 更新成 need+1。每一步都让当前值取得满足条件的最小值,不会让后面变得更难,所以得到的总增加次数就是最少操作次数。
参考代码
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
long long ans = 0;
long long need = (long long)-4e18;
for (int i = 1; i <= n; i++) {
long long x;
cin >> x;
if (i == 1) {
need = x;
continue;
}
long long want = need + 1;
if (x < want) {
ans += (want - x);
need = want;
} else {
need = x;
}
}
cout << ans << "\n";
return 0;
}
T3
简要题解
把每条边的权值只看奇偶即可。任选 1 号点为根做一次 DFS,设 pref[u] 表示从根到 u 这条路径上边权奇偶的异或(也就是边权和 mod 2),那么任意两点 u,v 的路径可以理解成“根到 u”和“根到 v”两条路把公共部分抵消掉,所以 u 到 v 的边权和奇偶就是 pref[u] XOR pref[v]。因此路径能量和为偶数当且仅当 pref[u]==pref[v],问题就变成数有多少对点落在同一桶里。统计 pref=0 的点数 cnt0、pref=1 的点数 cnt1,答案是 cnt0*(cnt0-1)/2 + cnt1*(cnt1-1)/2。
参考代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200000 + 5;
int n;
vector<pair<int,int>> g[MAXN];
long long cnt0, cnt1;
void dfs(int u, int fa, int p) {
if (p == 0) cnt0++;
else cnt1++;
for (int i = 0; i < (int)g[u].size(); i++) {
int v = g[u][i].first;
int w = g[u][i].second;
if (v == fa) continue;
dfs(v, u, p ^ w);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
for (int i = 1; i <= n; i++) g[i].clear();
for (int i = 1; i <= n - 1; i++) {
int u, v, c;
cin >> u >> v >> c;
int w = (c ) % 2;
g[u].push_back(make_pair(v, w));
g[v].push_back(make_pair(u, w));
}
cnt0 = 0;
cnt1 = 0;
dfs(1, 0, 0);
long long ans = cnt0 * (cnt0 - 1) / 2 + cnt1 * (cnt1 - 1) / 2;
cout << ans << "\n";
return 0;
}
T4
简要题解
把“是否已经发生转折”当作状态即可:把每个点 拆成两层 、,其中 表示还在“不下降阶段”, 表示已经进入“不上升阶段”。从 走边 时,若 就转移到 (继续不下降),否则这一步就是第一次下降,转移到 ;从 出发则只能走满足 的边并转移到 (保持不上升)。这样所有从 出发的路径恰好对应“先不下降再不上升、至多一次转折”的合法路径,边权仍为 ,于是对这个 状态图直接跑一遍 Dijkstra,得到 ,答案为 ,两者都不可达则输出 。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll INF = (ll)4e18;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, s, t;
cin >> n >> m >> s >> t;
vector<int> h(n + 1);
for (int i = 1; i <= n; i++) cin >> h[i];
vector<vector<pair<int,int>>> g(n + 1);
for (int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
vector<ll> d0(n + 1, INF), d1(n + 1, INF);
priority_queue<pair<ll,int>, vector<pair<ll,int>>, greater<pair<ll,int>>> pq;
d0[s] = 0;
pq.push({0, s});
pq.push({0, s + n}); // 允许一开始就进入“下降阶段”(t=0 的情况)
while (!pq.empty()) {
ll dist = pq.top().first;
int id = pq.top().second;
pq.pop();
int phase = 0;
int u = id;
if (id > n) {
phase = 1;
u = id - n;
}
if (phase == 0) {
if (dist != d0[u]) continue;
} else {
if (dist != d1[u]) continue;
}
for (int i = 0; i < (int)g[u].size(); i++) {
int v = g[u][i].first;
int w = g[u][i].second;
ll nd = dist + (ll)w;
if (phase == 0) {
if (h[v] >= h[u]) {
if (nd < d0[v]) {
d0[v] = nd;
pq.push({nd, v});
}
} else {
if (nd < d1[v]) {
d1[v] = nd;
pq.push({nd, v + n});
}
}
} else {
if (h[v] <= h[u]) {
if (nd < d1[v]) {
d1[v] = nd;
pq.push({nd, v + n});
}
}
}
}
}
ll ans = min(d0[t], d1[t]);
if (ans >= INF / 2) cout << -1 << "\n";
else cout << ans << "\n";
return 0;
}
T5
简要题解
因为允许选任意非空子序列,取任意单个元素就有 ,此时没有相邻前缀可比较,必然 ,因此
接下来只需统计有多少非空子序列能让前缀乘积符号序列 恒定不变(也就是整个子序列的“符号变化次数”仍为 )。
若前缀符号恒为 ,则首个被选元素必须满足 。一旦首元素为 ,之后无论再选哪些位置,所有前缀乘积都为 ,因此以位置 作为首个 的贡献为 ,总贡献为
若前缀符号恒为 ,则子序列只能由正数构成。设
c_+=\left|\left{i\mid 1\le i\le n,\ a_i>0\right}\right|,则非空正数子序列个数为
若前缀符号恒为 ,则首元素必须为负数,之后只能继续选正数(再选负数会使前缀符号翻回 ,选到 会使前缀符号变为 )。对每个负数位置 作为首元素,其后可选的仅为 之后的正数位置。记
c_+(i)=\left|\left{j\mid i则以该负数为首元素的贡献为 ,负号类总贡献为
三类子序列互不重叠,因此达到 的子序列总数为
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
ll M;
cin >> n >> M;
vector<ll> a(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
if (M == 1) {
cout << 0 << " " << 0 << "\n";
return 0;
}
vector<ll> p2(n + 1);
p2[0] = 1 % M;
for (int i = 1; i <= n; i++) p2[i] = (p2[i - 1] * 2) % M;
vector<int> suf(n + 3, 0);
for (int i = n; i >= 1; i--) suf[i] = suf[i + 1] + (a[i] > 0);
ll ans = 0;
ll wp = (p2[suf[1]] - 1) % M;
if (wp < 0) wp += M;
ans += wp;
ans %= M;
for (int i = 1; i <= n; i++) {
if (a[i] == 0) {
ans += p2[n - i];
ans %= M;
}
}
for (int i = 1; i <= n; i++) {
if (a[i] < 0) {
ans += p2[suf[i + 1]];
ans %= M;
}
}
cout << 0 << " " << ans % M << "\n";
return 0;
}
T6
简要题解
把答案记为半径 :如果能选两个中心,使每个点到最近中心的距离都不超过 ,就说明 可行,且 越大越容易可行,所以可以对 二分。关键是实现 check(R):把树以 为根做一次 DFS,从叶子往上处理子树。对每个点 维护两件事:mx 表示在 的子树里“还没被任何中心覆盖到”的点到 的最远距离(若子树已经全覆盖则记为 ),near 表示子树内最近的中心到 的距离(没有中心则为 )。初始时把每个点自身当作未覆盖点,所以叶子的 mx=0。合并儿子时,未覆盖最远距离就是各儿子 mx+1 的最大值,最近中心距离就是各儿子 near+1 的最小值。若当前子树里已经有中心并且它能覆盖到这棵子树中最远的未覆盖点,即满足 ,则说明整棵子树都被兜住,把 mx 置为 。否则如果 mx 已经等于 ,表示这些未覆盖点再往父亲走就会超过半径,不得不在 放一个中心:中心数加一,并把 near=0,mx=-1。DFS 结束后,如果根的 mx 仍不是 ,说明还有点没被覆盖,再在根补一个中心。最终统计放下的中心数是否 ,即可判断 是否可行。一次 check 是 ,二分 次,所以总复杂度 。
参考代码
#include <bits/stdc++.h>
using namespace std;
int n;
vector<vector<int>> g;
int used;
pair<int,int> dfs(int u, int fa, int R) {
const int INF = 1000000000;
int mx = 0;
int near = INF;
for (int v : g[u]) {
if (v == fa) continue;
auto res = dfs(v, u, R);
int cmx = res.first;
int cnear = res.second;
if (cmx != -1) mx = max(mx, cmx + 1);
if (cnear < INF) near = min(near, cnear + 1);
}
if (near < INF && mx != -1 && near + mx <= R) {
mx = -1;
}
if (mx == R) {
used++;
near = 0;
mx = -1;
}
return {mx, near};
}
bool ok(int R) {
used = 0;
auto res = dfs(1, 0, R);
if (res.first != -1) used++;
return used <= 2;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
g.assign(n + 1, {});
for (int i = 1; i <= n - 1; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
if (n == 1) {
cout << 0 << "\n";
return 0;
}
int l = 0, r = n;
while (l < r) {
int mid = (l + r) / 2;
if (ok(mid)) r = mid;
else l = mid + 1;
}
cout << l << "\n";
return 0;
}
全部评论 7
话说老师咋没弄格式
2026-02-04 来自 浙江
1后续会修正
2026-02-04 来自 浙江
1
占领评论区
2026-02-04 来自 浙江
1
1周前 来自 浙江
0
2026-02-06 来自 浙江
0
2026-02-06 来自 浙江
0
2026-02-06 来自 浙江
0有点意思
2026-02-06 来自 浙江
0




































有帮助,赞一个