清华大学—计算机考研机试真题(1)

四季读书网 7 0
清华大学—计算机考研机试真题(1)

考点总结

主要考察线段树、离散化、二分查找、快速幂、拉格朗日插值法、线性递推、大模拟、广度优先搜索等。每个题目至少考察2个知识点,因此要注意灵活组合运用。清华机试的特点是:数学推导 + 数据结构 + 大模拟三足鼎立,细节多、复杂度门槛明确。

真题详解

面试

问题描述

生活在在外星球X上的小Z想要找一些小朋友组成一个舞蹈团,于是他在网上发布了信息,一共有 n个人报名面试。

面试必须按照报名的顺序依次进行。

小Z可以选择在面试完若干小朋友以后,在所有已经面试过的小朋友中进行任意顺序的挑选,以组合成一个舞蹈团。

虽然说是小朋友,但是外星球X上的生态环境和地球上的不太一样,这些小朋友的身高可能相差很大。

小Z希望组建的这个舞蹈团要求至少有 m个小朋友,并且这些小朋友的最高身高和最低身高之差不能超过 k个长度单位。

现在知道了这些小朋友的身高信息,问小Z至少要面试多少小朋友才能在已经面试过的小朋友中选出不少于 m个组成舞蹈团。

输入格式

第一行 3个整数 n,m,k,意义见题面描述;

第二行 n个整数,第 i个数 hi表示第 i个报名面试的小朋友的身高。

输出格式

如果可以选出舞蹈团,输出至少要面试多少人;否则输出 impossible。

数据范围

清华大学—计算机考研机试真题(1)-第1张图片-四季读书网

输入样例

6 3 5

170 169 175 171 180 175

输出样例

4

思考

对于已经面试过的人,若能选出至少 m 个人且最高身高与最低身高之差不超过 k,等价于:

存在一个长度为 k的身高区间 [x,x+k],其中包含至少 m个已面试的人。

因为区间左端点 x一定可以取为某一个人的身高。

所以把所有出现过的身高离散化,线段树维护:

以每个离散化身高作为区间左端点时,该窗口内有多少个已面试的人;

每次加入一个身高 h,它会影响所有满足x≤h≤x+k的左端点 x,即h−k≤x≤h

对这些左端点区间加 1;

查询全局最大值,若最大值 ≥m,说明当前前缀已经可以组成舞蹈团。

时间复杂度:

O(nlogn)。

代码

#include<bits/stdc++.h>using namespace std;struct SegTree {    int n;    vector<int> mx, lazy;    SegTree(int _n = 0) {        init(_n);    }    voidinit(int _n){        n = _n;        mx.assign(4 * n + 5, 0);        lazy.assign(4 * n + 5, 0);    }    voidapply(int p, int v){        mx[p] += v;        lazy[p] += v;    }    voidpush(int p){        if (lazy[p] != 0) {            apply(p << 1, lazy[p]);            apply(p << 1 | 1, lazy[p]);            lazy[p] = 0;        }    }    voidadd(int p, int l, int r, int ql, int qr, int v){        if (ql <= l && r <= qr) {            apply(p, v);            return;        }        push(p);        int mid = (l + r) >> 1;        if (ql <= mid) add(p << 1, l, mid, ql, qr, v);        if (qr > mid) add(p << 1 | 1, mid + 1, r, ql, qr, v);        mx[p] = max(mx[p << 1], mx[p << 1 | 1]);    }    voidadd(int l, int r, int v){        if (l <= r) add(1, 0, n - 1, l, r, v);    }    intgetMax()const{        return mx[1];    }};intmain(){    ios::sync_with_stdio(false);    cin.tie(nullptr);    int n, m;    long long k;    cin >> n >> m >> k;    vector<longlong> h(n);    vector<long long> vals;    for (int i = 0; i < n; i++) {        cin >> h[i];        vals.push_back(h[i]);    }    sort(vals.begin(), vals.end());    vals.erase(unique(vals.begin(), vals.end()), vals.end());    int sz = vals.size();    SegTree seg(sz);    for (int i = 0; i < n; i++) {        // 当前身高 h[i] 会影响左端点 x 满足:        // h[i] - k <= x <= h[i]        long long leftVal = h[i] - k;        int l = lower_bound(vals.begin(), vals.end(), leftVal) - vals.begin();        int r = upper_bound(vals.begin(), vals.end(), h[i]) - vals.begin() - 1;        seg.add(l, r, 1);        if (seg.getMax() >= m) {            cout << i + 1 << '\n';            return 0;        }    }    cout << "impossible\n";    return 0;}

多项式求和

问题描述

小K最近刚刚习得了一种非常酷炫的多项式求和技巧,可以对某几类特殊的多项式进行运算。

非常不幸的是,小K发现老师在布置作业时抄错了数据,导致一道题并不能用刚学的方法来解,于是希望你能帮忙写一个程序跑一跑。

给出一个 m阶多项式

清华大学—计算机考研机试真题(1)-第2张图片-四季读书网

对给定的正整数 a,求

清华大学—计算机考研机试真题(1)-第3张图片-四季读书网

由于这个数可能比较大,所以你只需计算 S(n)对 10^9+7取模后的值(即计算除以 10^9+7后的余数)。

输入格式

第一行包含三个整数 n,m,a。

第二行包含m+1个整数,b0,b1,…,bm描述给定多项式的系数。

对于所有数据,1≤a,bi≤10^9。

输出格式

输出一行一个数,表示 S(n)对 10^9+7取模后的结果。

数据范围

清华大学—计算机考研机试真题(1)-第4张图片-四季读书网

输入样例

5 2 3

1 1 1

输出样例

9658

思考

清华大学—计算机考研机试真题(1)-第5张图片-四季读书网
清华大学—计算机考研机试真题(1)-第6张图片-四季读书网

代码

#include <bits/stdc++.h>using namespace std;typedef long long ll;const int MOD = 1000000007;ll modpow(ll a, ll e) {    ll r = 1;    while (e) {        if (e & 1) r = r * a % MOD;        a = a * a % MOD;        e >>= 1;    }    return r;}ll inv(ll x) {    return modpow(x, MOD - 2);}// 已知 y[i] = S(i),i=0..d,求 S(n)ll lagrange(const vector<ll>& y, ll n) {    int d = (int)y.size() - 1;    if (n <= d) return y[n];    ll ans = 0;    for (int i = 0; i <= d; i++) {        ll num = y[i];        ll den = 1;        for (int j = 0; j <= d; j++) {            if (i == j) continue;            num = num * ((n - j) % MOD + MOD) % MOD;            den = den * ((i - j) % MOD + MOD) % MOD;        }        ans = (ans + num * inv(den)) % MOD;    }    return ans;}int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    ll n;    int m;    ll a;    cin >> n >> m >> a;    vector<ll> b(m + 1);    for (int i = 0; i <= m; i++) cin >> b[i];    // 计算 f(k)    auto calc_f = [&](ll k) -> ll {        ll val = 0;        ll x = k % MOD;        for (int i = m; i >= 0; i--) {            val = (val * x + b[i]) % MOD;        }        return val;    };    if (a % MOD == 1) {        // S(n) 是 m+1 次多项式        int d = m + 1;        vector<ll> y(d + 1);        ll sum = 0;        for (int i = 0; i <= d; i++) {            sum = (sum + calc_f(i)) % MOD;            y[i] = sum;        }        cout << lagrange(y, n) << '\n';    } else {        // 预处理组合数        vector<vector<ll>> C(m + 1, vector<ll>(m + 1, 0));        for (int i = 0; i <= m; i++) {            C[i][0] = C[i][i] = 1;            for (int j = 1; j < i; j++) {                C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % MOD;            }        }        // 求 Q(x) = q[0] + q[1]x + ... + q[m]x^m        // 满足 a Q(x+1) - Q(x) = f(x)        vector<ll> q(m + 1, 0);        ll inv_am1 = inv((a - 1 + MOD) % MOD);        for (int i = m; i >= 0; i--) {            ll sum = 0;            for (int j = i + 1; j <= m; j++) {                sum = (sum + q[j] * C[j][i]) % MOD;            }            ll rhs = (b[i] - a * sum % MOD + MOD) % MOD;            q[i] = rhs * inv_am1 % MOD;        }        ll Q0 = q[0];        // 计算 Q(n+1)        ll x = (n + 1) % MOD;        ll Qn = 0;        for (int i = m; i >= 0; i--) {            Qn = (Qn * x + q[i]) % MOD;        }        ll an = modpow(a % MOD, n + 1);        ll ans = (an * Qn % MOD - Q0 + MOD) % MOD;        cout << ans << '\n';    }    return 0;}

扫雷

问题描述

扫雷(minesweeper)是一个有趣的单人益智类游戏,游戏目标是在最短的时间内根据棋盘上的提示信息,找出所有非雷方块,同时避免踩到地雷。随着桌面操作系统Windows的流行,其自带的扫雷游戏也因为有趣的玩法、精致的画面受到大家的欢迎。

小L的电脑上曾经也有一个扫雷游戏,它和主流的扫雷游戏基本相似,但是有一些不同的地方,具体介绍如下:

游戏开始时,玩家可以看到 N×M个整齐排列的空白方块,玩家须根据棋盘已有的信息,运用逻辑推理来推断哪些方块含或不含地雷。

玩家可以用鼠标左键点击空白方块,表示推断这个方块没有地雷,尝试探明它。

如果玩家点开没有地雷的方块,会有一个数字显现其上,这个数字代表着八连通的相邻方块有多少颗地雷(至多为 8)

如果这个方块八连通的方块中没有地雷(也即,方块显示的数字为 0),则系统会自动帮玩家点开它相邻的方块,这个过程可能会引起连锁反应。

如果玩家点开有地雷的方块,则游戏结束,玩家失败。

玩家可在推测有地雷的方块上点鼠标右键,表示放置旗帜来标明地雷的位置;在有旗帜的方块上再次点击右键,会使旗帜消失,成为空白的方块。在已标明旗帜的方块点击左键,方块不会有任何的变动。若在游戏进行中错置旗帜,可以用右键来改变方块状态。

玩家可以在一个已探明的方块上同时点击左键及右键。此时,如果方块相邻的 8

 个方块放置旗帜的数目与方块上的数字相同,那么周围未探明的方块就会自动打开。然而,玩家若错置旗帜位置,此动作可能会打开真正藏有地雷的方块,导致游戏失败。不过这样的点击动作可加快游戏速度以便得到高分。

然而,年代久远,小 L 已经找不到当年陪他度过十年求学时光的扫雷游戏了,于是他找到了精通编程的你,希望你能帮他写一个简单的扫雷游戏,帮助他回忆那些快乐时光。

具体来说,你的程序应该读入一个地雷布置图。然后读入用户的每一次游戏操作,并在每次操作后给用户以反馈,帮助用户进行游戏。

输入格式

约定:我们用坐标 (x,y)表示棋盘第 x行、第 y列的方块。

第一行用空格隔开的两个整数 n,m,表示棋盘的规模。

接下来 n行,每行一个长为 m的字符串,描述棋盘,其中第 i行的第 j个字符表示棋盘的方块 (i,j)。为 * 表示方块里有一个地雷,为 . 表示方块是安全的。

接下来每一行按时间顺序描述每一次用户操作,直到文件结束。每一行的格式如下:

1、首先读入一个字符串,表示这次操作的内容:

Flag:表示右键点击某个方块,插上/撤销一面旗帜。

Sweep:表示左键点击某个方块,判断这个方块没有地雷,要探明之。

DSweep:表示左右键同时点击某个方块,尝试探明与它相邻的方块。

Quit:表示放弃本局游戏并退出。

2、若操作不为 Quit,则之后有空格隔开的两个整数 x,y,表示这次操作的坐标为 (x,y),保证 1≤x≤n, 1≤y≤m。

输入数据保证存在有且仅有一次 Quit 操作。

输出格式

清华大学—计算机考研机试真题(1)-第7张图片-四季读书网
清华大学—计算机考研机试真题(1)-第8张图片-四季读书网
清华大学—计算机考研机试真题(1)-第9张图片-四季读书网

数据范围

我们令 n,m表示棋盘的规模,q表示输入的操作次数,有以下约定:

清华大学—计算机考研机试真题(1)-第10张图片-四季读书网

输入样例

3 3

...

..*

...

Sweep 1 1

DSweep 1 2

Flag 1 3

Flag 2 3

DSweep 1 2

Sweep 1 3

Flag 1 1

DSweep 1 3

Flag 1 3

DSweep 1 2

DSweep 1 2

Sweep 3 3

Quit

输出样例

6 cell(s) detected

1 1 0

1 2 1

2 1 0

2 2 1

3 1 0

3 2 1

failed

success

success

failed

flagged

swept

not swept

cancelled

1 cell(s) detected

1 3 1

no cell detected

1 cell(s) detected

3 3 1

finish

total step 12

思考

扫雷展开:手写 BFS 队列

doSweep(x, y) 的核心:

若已探明 → 直接成功;若是雷 → 返回 false(boom)。

标记 opened,计算 num,把坐标塞进 newCells(本次新探明列表)。

若 num == 0,把该格入队。

队列非空时,取出队首,枚举 8 邻居:

先判坐标 nx∈[1,n], ny∈[1,m] 是否合法;

跳过已探明;

清除旗帜 → 标记探明 → 计算 num → 记录 → 若 num==0 入队。

用全局预分配数组 qx[]/qy[] 当头尾指针队列,避免反复 queue<pair<>> 的动态分配与 STL 开销。newCells 也用全局数组记录本次新探明的 id,最后统一排序输出。

代码

#include<bits/stdc++.h>using namespace std;const int MAXN = 1100000;int n, m, W;vector<char> mine, opened, flagged;vector<int> num;int qx[MAXN], qy[MAXN];int newCells[MAXN];int newCnt;long long totalStep = 0;int safeTotal = 0;int openedSafe = 0;inlineintid(int x, int y){ return x * W + y; }inlineintcalcNum(int x, int y){    int cnt = 0;    int base = id(x, y);    for (int dx = -1; dx <= 1; ++dx) {        int bx = base + dx * W;        for (int dy = -1; dy <= 1; ++dy) {            if (dx == 0 && dy == 0) continue;            cnt += mine[bx + dy];        }    }    return cnt;}intcountFlags(int x, int y){    int cnt = 0;    int base = id(x, y);    for (int dx = -1; dx <= 1; ++dx) {        int bx = base + dx * W;        for (int dy = -1; dy <= 1; ++dy) {            if (dx == 0 && dy == 0) continue;            cnt += flagged[bx + dy];        }    }    return cnt;}booldoSweep(int sx, int sy){    int sid = id(sx, sy);    if (opened[sid]) return true;    if (mine[sid]) return false;    int head = 0, tail = 0;    opened[sid] = 1;    int c = calcNum(sx, sy);    num[sid] = c;    newCells[newCnt++] = sid;    ++openedSafe;    if (c == 0) {        qx[tail] = sx;        qy[tail] = sy;        ++tail;    }    while (head < tail) {        int x = qx[head], y = qy[head];        ++head;        for (int dx = -1; dx <= 1; ++dx) {            for (int dy = -1; dy <= 1; ++dy) {                if (dx == 0 && dy == 0) continue;                int nx = x + dx, ny = y + dy;                if (nx < 1 || nx > n || ny < 1 || ny > m) continue;   // 边界检查                int nid = id(nx, ny);                if (opened[nid]) continue;                flagged[nid] = 0;                opened[nid] = 1;                int nc = calcNum(nx, ny);                num[nid] = nc;                newCells[newCnt++] = nid;                ++openedSafe;                if (nc == 0) {                    qx[tail] = nx;                    qy[tail] = ny;                    ++tail;                }            }        }    }    return true;}voidoutputNewCells(){    if (newCnt == 0) {        printf("no cell detected\n");        return;    }    printf("%d cell(s) detected\n", newCnt);    sort(newCells, newCells + newCnt);    for (int i = 0; i < newCnt; ++i) {        int code = newCells[i];        int x = code / W;        int y = code % W;        printf("%d %d %d\n", x, y, num[code]);    }}voidendGame(constchar* result){    printf("%s\n", result);    printf("total step %lld\n", totalStep);}intmain(){    scanf("%d %d", &n, &m);    W = m + 2;    int totalCells = (n + 2) * W;    mine.assign(totalCells, 0);    opened.assign(totalCells, 0);    flagged.assign(totalCells, 0);    num.assign(totalCells, 0);    int mineCount = 0;    char buf[1005];    for (int i = 1; i <= n; ++i) {        scanf("%s", buf);        for (int j = 1; j <= m; ++j) {            if (buf[j - 1] == '*') {                mine[id(i, j)] = 1;                ++mineCount;            }        }    }    safeTotal = n * m - mineCount;    char op[20];    while (scanf("%s", op) != EOF) {        if (op[0] == 'Q') {            endGame("give up");            return 0;        }        int x, y;        scanf("%d %d", &x, &y);        ++totalStep;        newCnt = 0;        if (op[0] == 'F') { // Flag            int idx = id(x, y);            if (opened[idx]) {                printf("swept\n");            } else {                if (flagged[idx]) {                    flagged[idx] = 0;                    printf("cancelled\n");                } else {                    flagged[idx] = 1;                    printf("success\n");                }            }        } else if (op[0] == 'S') { // Sweep            int idx = id(x, y);            if (opened[idx]) {                printf("swept\n");            } else if (flagged[idx]) {                printf("flagged\n");            } else {                bool ok = doSweep(x, y);                if (!ok) {                    printf("boom\n");                    endGame("game over");                    return 0;                }                outputNewCells();                if (openedSafe == safeTotal) {                    endGame("finish");                    return 0;                }            }        } else if (op[0] == 'D') { // DSweep            int idx = id(x, y);            if (!opened[idx]) {                printf("not swept\n");            } else {                int c = num[idx];                if (c == 0) {                    printf("failed\n");                } else {                    int flags = countFlags(x, y);                    if (flags != c) {                        printf("failed\n");                    } else {                        bool boom = false;                        for (int dx = -1; dx <= 1 && !boom; ++dx) {                            int nx = x + dx;                            for (int dy = -1; dy <= 1 && !boom; ++dy) {                                if (dx == 0 && dy == 0) continue;                                int ny = y + dy;                                if (nx < 1 || nx > n || ny < 1 || ny > m) continue;                                int nid = id(nx, ny);                                if (opened[nid]) continue;                                if (flagged[nid]) continue;                                if (!doSweep(nx, ny)) {                                    boom = true;                                }                            }                        }                        if (boom) {                            printf("boom\n");                            endGame("game over");                            return 0;                        }                        outputNewCells();                        if (openedSafe == safeTotal) {                            endGame("finish");                            return 0;                        }                    }                }            }        }    }    return 0;}

抱歉,评论功能暂时关闭!