第 101 次训练 · 题解 ¶
共八题(A–H),难度从入门到数据结构压轴逐级递进。
每题附 C++、Java、Python 参考实现。
题目总览 ¶
| 题号 | 分值 | 洛谷 ID | 题目 | 核心知识点 |
|---|---|---|---|---|
| A | 1 | U722360 | 嗯对 | 字符串拼接、输入输出 |
| B | 1 | U722380 | 嗯对2 | 数位和、整除性质 |
| C | 2 | U724371 | 嘉豪的盯盘 | 时间换算、取模 |
| D | 2 | U724372 | 嘉豪的密码 | 字符串循环移位 |
| E | 2 | U724373 | 嘉豪的全对 | 前缀和、区间查询 |
| F | 3 | U724375 | 嘉豪的组队 | 哈希表、两数之和 |
| G | 4 | U724376 | 嘉豪的爬楼 | 两维 DP、滚动数组 |
| H | 5 | U724463 | 嘉豪的低谷 | 单调栈、贡献法 |
A题 · 嗯对(1 分) ¶
| 项目 | 内容 |
|---|---|
| 洛谷 ID | U722360 |
| 标签 | 模拟、字符串、输入输出 |
| 复杂度 | $O(\text{总长度})$ |
题目大意 ¶
给定 $N$ 行字符串,按输入顺序首尾相连拼成一行输出。
思路 ¶
循环读入 $N$ 行字符串,依次累加拼接到结果串末尾,最后一次性输出。注意 $N$ 最大 $10^{18}$,用 long long 存储(实际数据总长度不超过 $2\times10^7$,正常读入即可)。
参考代码 ¶
C++ ¶
#include <iostream>
#include <string>
using namespace std;
int main() {
long long N;
cin >> N;
string ans;
for (long long i = 0; i < N; i++) {
string s;
cin >> s;
ans += s;
}
cout << ans << endl;
return 0;
}
Java ¶
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long N = sc.nextLong();
StringBuilder ans = new StringBuilder();
for (long i = 0; i < N; i++) {
ans.append(sc.next());
}
System.out.println(ans);
}
}
Python ¶
import sys
def main():
data = sys.stdin.read().split()
n = int(data[0])
print(''.join(data[1:1 + n]))
if __name__ == "__main__":
main()
B题 · 嗯对2(1 分) ¶
| 项目 | 内容 |
|---|---|
| 洛谷 ID | U722380 |
| 标签 | 数论、整除 |
| 复杂度 | $O(K)$ |
题目大意 ¶
给定一个 $K$ 位数字串 $S$,判断它代表的数字是否是 $9$ 的倍数。
思路 ¶
一个数能被 $9$ 整除,当且仅当其各位数字之和能被 $9$ 整除。由于 $K$ 可达 $10^6$,不能把整串转成整数,直接遍历字符累加数位和,再判断 sum % 9 == 0。
参考代码 ¶
C++ ¶
#include <iostream>
#include <string>
using namespace std;
int main() {
long long K;
string S;
cin >> K >> S;
long long sum = 0;
for (char c : S) sum += (c - '0');
cout << (sum % 9 == 0 ? "Yes" : "No") << endl;
return 0;
}
Java ¶
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long K = sc.nextLong();
String S = sc.next();
long sum = 0;
for (char c : S.toCharArray()) {
sum += (c - '0');
}
System.out.println(sum % 9 == 0 ? "Yes" : "No");
}
}
Python ¶
import sys
def main():
data = sys.stdin.read().split()
s = data[1]
print("Yes" if sum(ord(c) - 48 for c in s) % 9 == 0 else "No")
if __name__ == "__main__":
main()
C题 · 嘉豪的盯盘(2 分) ¶
| 项目 | 内容 |
|---|---|
| 洛谷 ID | U724371 |
| 标签 | 时间换算、取模、格式化输出 |
| 复杂度 | $O(1)$ |
题目大意 ¶
给定 24 小时制时间 HH:MM 和分钟数 $m$,求 $m$ 分钟后的时间。
思路 ¶
换算成总分钟数:total = HH * 60 + MM + m,再对一天($1440$ 分钟)取模,输出 total/60 与 total%60,补前导零。
参考代码 ¶
C++ ¶
#include <cstdio>
int main() {
int HH, MM, m;
scanf("%d:%d", &HH, &MM);
scanf("%d", &m);
int total = HH * 60 + MM + m;
total %= 1440;
printf("%02d:%02d\n", total / 60, total % 60);
return 0;
}
Java ¶
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String t = sc.next();
int m = sc.nextInt();
String[] parts = t.split(":");
int HH = Integer.parseInt(parts[0]);
int MM = Integer.parseInt(parts[1]);
int total = (HH * 60 + MM + m) % 1440;
System.out.printf("%02d:%02d%n", total / 60, total % 60);
}
}
Python ¶
import sys
def main():
data = sys.stdin.read().split()
hh, mm = map(int, data[0].split(':'))
m = int(data[1])
total = (hh * 60 + mm + m) % 1440
print(f"{total // 60:02d}:{total % 60:02d}")
if __name__ == "__main__":
main()
D题 · 嘉豪的密码(2 分) ¶
| 项目 | 内容 |
|---|---|
| 洛谷 ID | U724372 |
| 标签 | 字符串、循环移位、取模 |
| 复杂度 | $O( |
题目大意 ¶
把字符串 $S$ 的每个小写字母在字母表内循环后移 $k$ 位,z 后移回到 a。
思路 ¶
对每个字符 $c$,c' = (c - 'a' + k) % 26 + 'a'。注意 $k$ 可达 $10^9$,先 k %= 26 再运算。
参考代码 ¶
C++ ¶
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
long long k;
cin >> s >> k;
int shift = k % 26;
for (char& c : s) {
c = (c - 'a' + shift) % 26 + 'a';
}
cout << s << endl;
return 0;
}
Java ¶
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.next();
long k = sc.nextLong();
int shift = (int)(k % 26);
StringBuilder sb = new StringBuilder();
for (char c : s.toCharArray()) {
sb.append((char)('a' + (c - 'a' + shift) % 26));
}
System.out.println(sb);
}
}
Python ¶
import sys
def main():
data = sys.stdin.read().split()
s = data[0]
k = int(data[1])
shift = k % 26
print(''.join(chr((ord(c) - 97 + shift) % 26 + 97) for c in s))
if __name__ == "__main__":
main()
E题 · 嘉豪的全对(2 分) ¶
| 项目 | 内容 |
|---|---|
| 洛谷 ID | U724373 |
| 标签 | 前缀和、区间查询 |
| 复杂度 | $O(n + q)$ |
题目大意 ¶
$n$ 个数 $a_1,\dots,a_n$,$q$ 次询问区间 $[l,r]$ 的和。
思路 ¶
预处理前缀和 pre[i] = a[1] + ... + a[i],询问回答 pre[r] - pre[l-1]。注意用 64 位整数。
参考代码 ¶
C++ ¶
#include <iostream>
#include <vector>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, q;
cin >> n >> q;
vector<ll> pre(n + 1, 0);
for (int i = 1; i <= n; i++) {
ll x;
cin >> x;
pre[i] = pre[i - 1] + x;
}
while (q--) {
int l, r;
cin >> l >> r;
cout << pre[r] - pre[l - 1] << '\n';
}
return 0;
}
Java ¶
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
int q = Integer.parseInt(st.nextToken());
long[] pre = new long[n + 1];
st = new StringTokenizer(br.readLine());
for (int i = 1; i <= n; i++) {
pre[i] = pre[i - 1] + Long.parseLong(st.nextToken());
}
StringBuilder sb = new StringBuilder();
while (q-- > 0) {
st = new StringTokenizer(br.readLine());
int l = Integer.parseInt(st.nextToken());
int r = Integer.parseInt(st.nextToken());
sb.append(pre[r] - pre[l - 1]).append('\n');
}
System.out.print(sb);
}
}
Python ¶
import sys
def main():
data = sys.stdin.buffer.read().split()
it = iter(data)
n = int(next(it))
q = int(next(it))
pre = [0] * (n + 1)
for i in range(1, n + 1):
pre[i] = pre[i - 1] + int(next(it))
out = []
for _ in range(q):
l = int(next(it))
r = int(next(it))
out.append(str(pre[r] - pre[l - 1]))
sys.stdout.write('\n'.join(out) + '\n')
if __name__ == "__main__":
main()
F题 · 嘉豪的组队(3 分) ¶
| 项目 | 内容 |
|---|---|
| 洛谷 ID | U724375 |
| 标签 | 哈希表、两数之和、组合计数 |
| 复杂度 | $O(n)$ |
题目大意 ¶
$n$ 个数中选两个下标 $(i<j)$,问有多少对满足 $a_i + a_j = K$。
思路 ¶
哈希表统计每个值的出现次数。遍历每个不同的 $x$,令 $y = K - x$:
- $x < y$:贡献
cnt[x] * cnt[y]; - $x = y$:贡献
cnt[x] * (cnt[x] - 1) / 2。
只处理 $x \le y$ 以免重复计数。
参考代码 ¶
C++ ¶
#include <iostream>
#include <unordered_map>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
ll K;
cin >> n >> K;
unordered_map<ll, ll> cnt;
for (int i = 0; i < n; i++) {
ll x;
cin >> x;
cnt[x]++;
}
ll ans = 0;
for (auto& [x, cx] : cnt) {
ll y = K - x;
if (cnt.count(y)) {
if (x < y) ans += cx * cnt[y];
else if (x == y) ans += cx * (cx - 1) / 2;
}
}
cout << ans << endl;
return 0;
}
Java ¶
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int n = Integer.parseInt(st.nextToken());
long K = Long.parseLong(st.nextToken());
Map<Long, Long> cnt = new HashMap<>();
st = new StringTokenizer(br.readLine());
for (int i = 0; i < n; i++) {
long x = Long.parseLong(st.nextToken());
cnt.put(x, cnt.getOrDefault(x, 0L) + 1);
}
long ans = 0;
for (Map.Entry<Long, Long> e : cnt.entrySet()) {
long x = e.getKey(), cx = e.getValue();
long y = K - x;
if (cnt.containsKey(y)) {
if (x < y) {
ans += cx * cnt.get(y);
} else if (x == y) {
ans += cx * (cx - 1) / 2;
}
}
}
System.out.println(ans);
}
}
Python ¶
import sys
from collections import Counter
def main():
data = sys.stdin.buffer.read().split()
n = int(data[0])
K = int(data[1])
cnt = Counter(map(int, data[2:2 + n]))
ans = 0
for x, cx in cnt.items():
y = K - x
if y not in cnt:
continue
if x < y:
ans += cx * cnt[y]
elif x == y:
ans += cx * (cx - 1) // 2
print(ans)
if __name__ == "__main__":
main()
G题 · 嘉豪的爬楼(4 分) ¶
| 项目 | 内容 |
|---|---|
| 洛谷 ID | U724376 |
| 标签 | 动态规划、爬楼梯变体、滚动数组 |
| 复杂度 | $O(n)$ 时间,$O(1)$ 空间 |
题目大意 ¶
$n$ 级台阶,每次跨 1 级或 2 级,但不能连续两次都跨 1 级,求走法总数(对 $10^9+7$ 取模)。
思路 ¶
“不能连续两个 1 级”意味着最后一步的类型会影响下一步,用两维 DP:
dp[i][0]:到达第 $i$ 级、最后一步跨 1 级的方案数;dp[i][1]:到达第 $i$ 级、最后一步跨 2 级的方案数。
转移:
dp[i][0] = dp[i-1][1](最后一步 1 级,前一步必须 2 级);dp[i][1] = dp[i-2][0] + dp[i-2][1](最后一步 2 级,前一步任意)。
用滚动数组维护 dp[i-2] 与 dp[i-1] 两组状态。
参考代码 ¶
C++ ¶
#include <iostream>
using namespace std;
const long long MOD = 1'000'000'007LL;
int main() {
int n;
cin >> n;
long long a0 = 0, a1 = 1; // dp[0]
long long b0 = a1, b1 = 0; // dp[1]
if (n == 0) { cout << 1 << endl; return 0; }
if (n == 1) { cout << (b0 + b1) % MOD << endl; return 0; }
for (int i = 2; i <= n; i++) {
long long c0 = b1;
long long c1 = (a0 + a1) % MOD;
a0 = b0; a1 = b1;
b0 = c0; b1 = c1;
}
cout << (b0 + b1) % MOD << endl;
return 0;
}
Java ¶
import java.util.Scanner;
public class Main {
static final long MOD = 1_000_000_007L;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
long a0 = 0, a1 = 1; // dp[0]
long b0 = a1, b1 = 0; // dp[1]
if (n == 0) { System.out.println(1); return; }
if (n == 1) { System.out.println((b0 + b1) % MOD); return; }
for (int i = 2; i <= n; i++) {
long c0 = b1;
long c1 = (a0 + a1) % MOD;
a0 = b0; a1 = b1;
b0 = c0; b1 = c1;
}
System.out.println((b0 + b1) % MOD);
}
}
Python ¶
import sys
MOD = 10 ** 9 + 7
def main():
n = int(sys.stdin.read())
if n == 0:
print(1)
return
a0, a1 = 0, 1 # dp[0]
b0, b1 = a1, 0 # dp[1]
if n == 1:
print((b0 + b1) % MOD)
return
for i in range(2, n + 1):
c0 = b1
c1 = (a0 + a1) % MOD
a0, a1 = b0, b1
b0, b1 = c0, c1
print((b0 + b1) % MOD)
if __name__ == "__main__":
main()
H题 · 嘉豪的低谷(5 分) ¶
| 项目 | 内容 |
|---|---|
| 洛谷 ID | U724463 |
| 标签 | 单调栈、贡献法、子区间统计 |
| 复杂度 | $O(n)$ |
题目大意 ¶
求所有连续子区间的最小值之和:
$$\sum_{l=1}^{n}\sum_{r=l}^{n}\min_{l\le i\le r}a_i$$
思路 ¶
不按子区间枚举,而是按”每个元素作为最小值”统计贡献。对位置 $i$:
- $L_i$:左边第一个严格小于 $a_i$ 的位置;
- $R_i$:右边第一个小于等于 $a_i$ 的位置。
以 $a_i$ 为最小值的子区间有 $(i-L_i)\times(R_i-i)$ 个,贡献 $a_i\times(i-L_i)\times(R_i-i)$。
用单调栈 $O(n)$ 求出所有 $L_i, R_i$。左边界用”严格小于”、右边界用”小于等于”,保证相等元素不重不漏。
参考代码 ¶
C++ ¶
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
vector<ll> a(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
vector<int> L(n + 1), R(n + 1);
stack<int> st;
for (int i = 1; i <= n; i++) {
while (!st.empty() && a[st.top()] >= a[i]) st.pop();
L[i] = st.empty() ? 0 : st.top();
st.push(i);
}
while (!st.empty()) st.pop();
for (int i = n; i >= 1; i--) {
while (!st.empty() && a[st.top()] > a[i]) st.pop();
R[i] = st.empty() ? n + 1 : st.top();
st.push(i);
}
ll ans = 0;
for (int i = 1; i <= n; i++) {
ans += a[i] * (i - L[i]) * (R[i] - i);
}
cout << ans << '\n';
return 0;
}
Java ¶
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(br.readLine());
long[] a = new long[n + 1];
StringTokenizer st = new StringTokenizer(br.readLine());
for (int i = 1; i <= n; i++) a[i] = Long.parseLong(st.nextToken());
int[] L = new int[n + 1], R = new int[n + 1];
ArrayDeque<Integer> stack = new ArrayDeque<>();
for (int i = 1; i <= n; i++) {
while (!stack.isEmpty() && a[stack.peek()] >= a[i]) stack.pop();
L[i] = stack.isEmpty() ? 0 : stack.peek();
stack.push(i);
}
stack.clear();
for (int i = n; i >= 1; i--) {
while (!stack.isEmpty() && a[stack.peek()] > a[i]) stack.pop();
R[i] = stack.isEmpty() ? n + 1 : stack.peek();
stack.push(i);
}
long ans = 0;
for (int i = 1; i <= n; i++) {
ans += a[i] * (i - L[i]) * (R[i] - i);
}
System.out.println(ans);
}
}
Python ¶
import sys
def main():
data = sys.stdin.buffer.read().split()
n = int(data[0])
a = [0] + [int(x) for x in data[1:1 + n]]
L = [0] * (n + 1)
R = [0] * (n + 1)
stack = []
for i in range(1, n + 1):
while stack and a[stack[-1]] >= a[i]:
stack.pop()
L[i] = stack[-1] if stack else 0
stack.append(i)
stack = []
for i in range(n, 0, -1):
while stack and a[stack[-1]] > a[i]:
stack.pop()
R[i] = stack[-1] if stack else n + 1
stack.append(i)
ans = 0
for i in range(1, n + 1):
ans += a[i] * (i - L[i]) * (R[i] - i)
print(ans)
if __name__ == "__main__":
main()
总结 ¶
| 题号 | 分值 | 题目 | 核心知识点 | 关键易错点 |
|---|---|---|---|---|
| A | 1 | 嗯对 | 字符串拼接 | $N$ 用 long long、输出不加多余换行 |
| B | 1 | 嗯对2 | 数位和整除 | 不能把整串转整数,用数位和 |
| C | 2 | 嘉豪的盯盘 | 时间换算 | 忘记 % 1440、不补前导零 |
| D | 2 | 嘉豪的密码 | 循环移位 | 忘记 k %= 26 导致溢出 |
| E | 2 | 嘉豪的全对 | 前缀和 | 未用 64 位整数、pre[0] 缺失 |
| F | 3 | 嘉豪的组队 | 哈希计数 | 去重(x < y)、相同值用组合数 |
| G | 4 | 嘉豪的爬楼 | 两维 DP | 状态设计(最后一步类型) |
| H | 5 | 嘉豪的低谷 | 单调栈 + 贡献法 | 相等元素左右边界去重 |
八道题从 1 分到 5 分,覆盖了:字符串拼接 → 整除性质 → 时间换算 → 循环移位 → 前缀和 → 哈希计数 → 动态规划 → 单调栈与贡献法,适合作为一次完整的入门到进阶训练。
💬 评论 (0)