← 回算法课 · 看不懂?问龟龟

ICT 编程赛 · 题型拆解与题目讲解

先说清楚一件事:华为 ICT 大赛编程赛的历年真题没有官方公开题库(这一点和蓝桥杯、CSP 不同),网上流传的多是回忆版或机构整理的"同类题"。所以这份文档做两件事:
① 把这类比赛必考的 8 种题型拆开讲,每题都给完整思路 + 仓颉代码;
② 告诉你去哪儿找真题、怎么用它练。

配套:《仓颉语法速成》(同目录)。代码里的 API 若与你本地 SDK 有差异,按那份文档的「以本地为准」标注改。

一、这类比赛考什么

华为 ICT 大赛的编程赛,本质是算法题(不是工程题、不是八股),语言可以用仓颉。

从题目风格看,和下面这些比赛高度相似,完全可以用它们的题来练:

比赛相似点用来练什么
蓝桥杯(省赛 A/B 组)题风最像:模拟 + 基础算法 + 少量数学首选替代训练
PTA 天梯赛(L1/L2)偏模拟、字符串、简单数据结构手速和读题
LeetCode 中等单题独立,模型清晰单个知识点
Codeforces Div2 A~C思维题、构造想题速度

结论:真题拿不到,就拿蓝桥杯省赛题和 PTA L1/L2 练——题型重合度最高。

常见考点分布(按出现频率)

题型频率难度说明
模拟 / 字符串处理极高低考细心,不考算法
排序 + 贪心高中开会/任务安排类
二分答案中中"最小化最大值"这类
前缀和 / 差分中低区间查询、区间加
BFS / DFS中中网格最短路、连通块
简单 DP中中背包、最长上升子序列
并查集低中连通性、朋友圈
数论 / 大数低中取模、gcd、进制
一句话:60% 的分在"模拟 + 排序贪心 + 前缀和",这些必须稳拿。

二、八种题型 · 逐题讲解

题型 1:模拟(字符串处理)——「统计单词」

题目:给一行英文(只有字母和空格),输出出现次数最多的单词及其次数。次数相同输出字典序最小的。

思路:切词 → 哈希计数 → 遍历比较。没有算法,只有细心。


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}")
}

易错:


题型 2:排序 + 贪心——「会议室安排」

题目: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)
}

易错:


题型 3:二分答案——「分绳子」

题目: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)
}

易错:


题型 4:前缀和 / 差分——「区间求和」

题目:给数组 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])
    }
}

易错:


题型 5:BFS——「网格最短路」

题目: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])
}

易错:


题型 6:简单 DP——「01 背包」

题目: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])
}

易错:


题型 7:并查集——「朋友圈」

题目: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)
}

易错:


题型 8:数学 / 取模——「快速幂」

题目:求 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))
}

易错:


三、真题从哪来

来源说明
学校/赛区通知附件ICT 大赛通常由学校组织报名,往届题可能作为"样题"发在群里,优先问带队老师要
华为 ICT 大赛官网/大赛公众号有时会放"赛道说明 + 样题"(以官方为准)
蓝桥杯省赛真题题型最接近,直接拿来练仓颉手感
PTA 天梯赛 L1/L2模拟题多,适合练读题和字符串
洛谷 / Codeforces练思路,语言用仓颉提交(洛谷支持仓颉)
我这边查到的公开信息里没有可引用的"编程赛真题 PDF",所以没给你贴假题。你要是能拿到学校发的样题/回忆版,发我,我按真题的风格给你逐题讲——那比我现在给的通用题准得多。

四、四周备赛节奏(针对 12 月比赛)

周目标每天做什么
第 1 周仓颉能写完整程序语法速成过一遍 + 5 个模板各跑通一次 + 每天 1 道模拟题
第 2 周基础算法补全前缀和/差分、二分、排序贪心,每天 2 题,必须用仓颉写
第 3 周图 + DPBFS/DFS、并查集、01 背包,每天 2 题
第 4 周套题模拟每 2 天一套(蓝桥杯省赛真题,3 小时),做完复盘:错的题重写一遍

每天固定 20 分钟:默写 readln 那串 + sort + ArrayList 三件套(肌肉记忆比看懂重要)。


五、比赛当天的策略


六、下一步