先说清楚一件事:华为 ICT 大赛编程赛的历年真题没有官方公开题库(这一点和蓝桥杯、CSP 不同),网上流传的多是回忆版或机构整理的"同类题"。所以这份文档做两件事:
① 把这类比赛必考的 8 种题型拆开讲,每题都给完整思路 + 仓颉代码;
② 告诉你去哪儿找真题、怎么用它练。
配套:《仓颉语法速成》(同目录)。代码里的 API 若与你本地 SDK 有差异,按那份文档的「以本地为准」标注改。
华为 ICT 大赛的编程赛,本质是算法题(不是工程题、不是八股),语言可以用仓颉。
从题目风格看,和下面这些比赛高度相似,完全可以用它们的题来练:
| 比赛 | 相似点 | 用来练什么 |
|---|---|---|
| 蓝桥杯(省赛 A/B 组) | 题风最像:模拟 + 基础算法 + 少量数学 | 首选替代训练 |
| PTA 天梯赛(L1/L2) | 偏模拟、字符串、简单数据结构 | 手速和读题 |
| LeetCode 中等 | 单题独立,模型清晰 | 单个知识点 |
| Codeforces Div2 A~C | 思维题、构造 | 想题速度 |
结论:真题拿不到,就拿蓝桥杯省赛题和 PTA L1/L2 练——题型重合度最高。
| 题型 | 频率 | 难度 | 说明 |
|---|---|---|---|
| 模拟 / 字符串处理 | 极高 | 低 | 考细心,不考算法 |
| 排序 + 贪心 | 高 | 中 | 开会/任务安排类 |
| 二分答案 | 中 | 中 | "最小化最大值"这类 |
| 前缀和 / 差分 | 中 | 低 | 区间查询、区间加 |
| BFS / DFS | 中 | 中 | 网格最短路、连通块 |
| 简单 DP | 中 | 中 | 背包、最长上升子序列 |
| 并查集 | 低 | 中 | 连通性、朋友圈 |
| 数论 / 大数 | 低 | 中 | 取模、gcd、进制 |
一句话:60% 的分在"模拟 + 排序贪心 + 前缀和",这些必须稳拿。
题目:给一行英文(只有字母和空格),输出出现次数最多的单词及其次数。次数相同输出字典序最小的。
思路:切词 → 哈希计数 → 遍历比较。没有算法,只有细心。
import std.env.*
import std.collection.*
main() {
let line = getStdIn().readln().getOrThrow().trim()
let words = line.split(" ")
var cnt = HashMap<String, Int64>()
for (w in words) {
if (w.size == 0) { continue }
cnt[w] = (cnt.get(w) ?? 0) + 1
}
var bestWord = ""
var bestCnt = -1
for (w in words) {
if (w.size == 0) { continue }
let c = cnt.get(w) ?? 0
// 次数更多,或者次数相同但字典序更小
if (c > bestCnt || (c == bestCnt && w < bestWord)) {
bestCnt = c
bestWord = w
}
}
println("${bestWord} ${bestCnt}")
}
易错:
w < bestWord 是字典序(仓颉 String 支持比较运算符)题目:n 个会议,每个有开始 s 和结束 e。一间会议室最多能安排几个不重叠的会议?
思路:按结束时间升序,能塞就塞。这是贪心的经典结论(证明略:结束得越早,留给后面的时间越多)。
import std.env.*
import std.sort.*
import std.collection.*
struct Meeting {
var s: Int64
var e: Int64
}
main() {
let n = Int64.parse(getStdIn().readln().getOrThrow().trim())
var arr = ArrayList<Meeting>()
for (i in 0..n) {
let p = getStdIn().readln().getOrThrow().trim().split(" ")
arr.add(Meeting(Int64.parse(p[0]), Int64.parse(p[1])))
}
// 按结束时间升序
sort(arr, by: {l: Meeting, r: Meeting => l.e.compare(r.e)})
var cnt = 0
var lastEnd = -1
for (m in arr) {
if (m.s >= lastEnd) { // 注意:端点相接算不重叠,所以是 >=
cnt++
lastEnd = m.e
}
}
println(cnt)
}
易错:
>=)by: 传比较函数题目:n 根绳子,长度 aᵢ。要剪出 k 根等长的绳子,求最大长度(整数)。
思路:答案有单调性——长度 L 能剪出来,更短的也能。所以二分 L,check 函数算"每根能剪几段"。
import std.env.*
import std.collection.*
main() {
let head = getStdIn().readln().getOrThrow().trim().split(" ")
let n = Int64.parse(head[0])
let k = Int64.parse(head[1])
var a = ArrayList<Int64>()
for (p in getStdIn().readln().getOrThrow().trim().split(" ")) {
a.add(Int64.parse(p))
}
var lo = 1
var hi = 0
for (x in a) { if (x > hi) { hi = x } }
var ans = 0
while (lo <= hi) {
let mid = (lo + hi) / 2
var seg = 0
for (x in a) { seg += x / mid }
if (seg >= k) {
ans = mid
lo = mid + 1 // 还能更长
} else {
hi = mid - 1
}
}
println(ans)
}
易错:
mid 不能是 0(除零会直接报错,所以 lo 从 1 开始)lo = mid + 1 / hi = mid - 1 配 while (lo <= hi)题目:给数组 a(n 个)和 q 个查询,每个查询 [l, r],求区间和。n, q ≤ 10⁵。
思路:暴力 O(nq) 会超时。预处理前缀和 pre[i] = a[0]+...+a[i-1],则区间和 = pre[r+1] - pre[l],O(1) 查询。
import std.env.*
import std.collection.*
main() {
let n = Int64.parse(getStdIn().readln().getOrThrow().trim())
var a = ArrayList<Int64>()
a.add(0) // 下标 1 开始,方便
for (p in getStdIn().readln().getOrThrow().trim().split(" ")) {
a.add(Int64.parse(p))
}
// pre[i] = a[1..i] 的和
var pre = ArrayList<Int64>()
pre.add(0)
for (i in 1..=n) {
pre.add(pre[i - 1] + a[i])
}
let q = Int64.parse(getStdIn().readln().getOrThrow().trim())
for (t in 0..q) {
let p = getStdIn().readln().getOrThrow().trim().split(" ")
let l = Int64.parse(p[0])
let r = Int64.parse(p[1])
println(pre[r] - pre[l - 1])
}
}
易错:
pre[0] = 0),否则 l=1 时越界d[l] += v; d[r+1] -= v,最后求前缀和还原题目:n×m 网格,. 可走 # 是墙,从 (0,0) 到 (n-1,m-1) 最少几步(四方向)。
思路:无权图最短路 = BFS。用队列逐层扩展。
import std.env.*
import std.collection.*
main() {
let head = getStdIn().readln().getOrThrow().trim().split(" ")
let n = Int64.parse(head[0])
let m = Int64.parse(head[1])
var grid = ArrayList<String>()
for (i in 0..n) {
grid.add(getStdIn().readln().getOrThrow().trim())
}
// dist 用二维:ArrayList 套 ArrayList
var dist = ArrayList<ArrayList<Int64>>()
for (i in 0..n) {
var row = ArrayList<Int64>()
for (j in 0..m) { row.add(-1) }
dist.add(row)
}
var qx = ArrayList<Int64>()
var qy = ArrayList<Int64>()
qx.add(0); qy.add(0); dist[0][0] = 0
var head2 = 0
let dx = [1, -1, 0, 0]
let dy = [0, 0, 1, -1]
while (head2 < qx.size) {
let x = qx[head2]
let y = qy[head2]
head2++
for (k in 0..4) {
let nx = x + dx[k]
let ny = y + dy[k]
if (nx < 0 || nx >= n || ny < 0 || ny >= m) { continue }
if (grid[nx][ny] == '#' || dist[nx][ny] != -1) { continue }
dist[nx][ny] = dist[x][y] + 1
qx.add(nx); qy.add(ny)
}
}
println(dist[n - 1][m - 1])
}
易错:
dist(出队才标记会重复入队,最坏超时)grid[nx][ny] 之前(否则可能越界)'#'(Rune)比,不是 "#"(String)题目:n 个物品,重量 wᵢ、价值 vᵢ,背包容量 W,每个只能拿一次,求最大价值。
思路:dp[j] = 容量 j 时的最大价值,倒着遍历 j(保证每件只用一次)。
import std.env.*
import std.collection.*
main() {
let head = getStdIn().readln().getOrThrow().trim().split(" ")
let n = Int64.parse(head[0])
let W = Int64.parse(head[1])
var w = ArrayList<Int64>()
var v = ArrayList<Int64>()
for (i in 0..n) {
let p = getStdIn().readln().getOrThrow().trim().split(" ")
w.add(Int64.parse(p[0]))
v.add(Int64.parse(p[1]))
}
var dp = ArrayList<Int64>()
for (j in 0..=W) { dp.add(0) }
for (i in 0..n) {
var j = W
while (j >= w[i]) { // 倒序!保证 0/1
let cand = dp[j - w[i]] + v[i]
if (cand > dp[j]) { dp[j] = cand }
j--
}
}
println(dp[W])
}
易错:
j >= w[i] 缺了会下标负题目:n 个人,m 对朋友关系(关系可传递),问有几个朋友圈。
思路:并查集,最后数"根节点是自己"的个数。
import std.env.*
import std.collection.*
var parent = ArrayList<Int64>()
func find(x: Int64): Int64 {
var r = x
while (parent[r] != r) { r = parent[r] }
// 路径压缩
var cur = x
while (parent[cur] != cur) {
let nxt = parent[cur]
parent[cur] = r
cur = nxt
}
r
}
func union(a: Int64, b: Int64) {
let ra = find(a)
let rb = find(b)
if (ra != rb) { parent[ra] = rb }
}
main() {
let head = getStdIn().readln().getOrThrow().trim().split(" ")
let n = Int64.parse(head[0])
let m = Int64.parse(head[1])
for (i in 0..n) { parent.add(i) }
for (t in 0..m) {
let p = getStdIn().readln().getOrThrow().trim().split(" ")
union(Int64.parse(p[0]), Int64.parse(p[1]))
}
var groups = 0
for (i in 0..n) {
if (find(i) == i) { groups++ }
}
println(groups)
}
易错:
parent 要在 main 里初始化(0..n-1)find 里不压缩也行(小数据),但压缩后更快题目:求 aᵇ mod p,b 可能到 10¹⁸。
思路:快速幂,O(log b)。ab 直接算会溢出(仓颉溢出会报错)。这道题是仓颉最容易翻车的地方—— 遇到大数会炸。
func fastPow(a: Int64, b: Int64, p: Int64): Int64 {
var base = a % p
var exp = b
var ans: Int64 = 1
while (exp > 0) {
if (exp % 2 == 1) {
ans = (ans * base) % p
}
base = (base * base) % p
exp = exp / 2
}
ans
}
main() {
println(fastPow(2, 1000000000000000000, 1000000007))
}
易错:
** 算大幂(溢出报错)% p,否则中间结果溢出p 是 1 时答案恒为 0(边界)| 来源 | 说明 |
|---|---|
| 学校/赛区通知附件 | ICT 大赛通常由学校组织报名,往届题可能作为"样题"发在群里,优先问带队老师要 |
| 华为 ICT 大赛官网/大赛公众号 | 有时会放"赛道说明 + 样题"(以官方为准) |
| 蓝桥杯省赛真题 | 题型最接近,直接拿来练仓颉手感 |
| PTA 天梯赛 L1/L2 | 模拟题多,适合练读题和字符串 |
| 洛谷 / Codeforces | 练思路,语言用仓颉提交(洛谷支持仓颉) |
我这边查到的公开信息里没有可引用的"编程赛真题 PDF",所以没给你贴假题。你要是能拿到学校发的样题/回忆版,发我,我按真题的风格给你逐题讲——那比我现在给的通用题准得多。
| 周 | 目标 | 每天做什么 |
|---|---|---|
| 第 1 周 | 仓颉能写完整程序 | 语法速成过一遍 + 5 个模板各跑通一次 + 每天 1 道模拟题 |
| 第 2 周 | 基础算法补全 | 前缀和/差分、二分、排序贪心,每天 2 题,必须用仓颉写 |
| 第 3 周 | 图 + DP | BFS/DFS、并查集、01 背包,每天 2 题 |
| 第 4 周 | 套题模拟 | 每 2 天一套(蓝桥杯省赛真题,3 小时),做完复盘:错的题重写一遍 |
每天固定 20 分钟:默写 readln 那串 + sort + ArrayList 三件套(肌肉记忆比看懂重要)。