GESP7级做题经验及题解

包括2023-12,2024-03,2024-06

更新记录

[2024-08-26 22:03] 完成了 <商品交易> 和 <纸牌游戏> 的题解与感受。
[2024-08-27 15:08] 完成了 <交流问题> 的题解与感受,并提供了所有题目的正确代码。
[2024-08-27 21:58] 完成了 <俄罗斯方块> 的题解与感受,补充了 <纸牌游戏> 的感受。
[2024-08-28 16:34] 完成了 <黑白翻转> 和 <区间乘积> 的题解与感受,完结撒花。
[2024-08-28 16:53] 补充了 <总结> 部分。

正确代码

洛谷 - 云剪贴板Github - Gist

商品交易 202312 A

洛谷题库 P10110

题解

本质上是求一个边权为 $1$ 的最短路。每进行一次交换,总资产就会少 $1$ 元。

根据题意,用手上的第 $x$ 种商品交换第 $y$ 种商品,花费 $v_y - v_x + 1$ 元,总资产变化为 $-v_x + v_y - (v_y - v_x + 1) = -1$。($-v_x$ 是第 $x$ 件商品没了,$+v_y$ 同理)

最后加上期望获得的商品的价格 $v_b$ 与原本持有的商品的价格 $v_a$ 的差即可。

感受

非常巧妙的一道最短路问题,算是一个简化版的 SPFA(由于边权为 $1$,所以不需要 st 数组防止重复入队)。

放段代码比较一下:

SPFA 模板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
int spfa() {
    memset(dist, 0x3f, sizeof dist);
    queue<int> q;
    dist[1] = 0; st[1] = true; q.push(1);
    while (!q.empty()) {
        int t = q.front(); q.pop();
        st[t] = false;
        for (int i = h[t]; i != -1; i = ne[i]) {
            int j = e[i];
            if (dist[j] > dist[t] + w[i]) {
                dist[j] = dist[t] + w[i];
                if (!st[j]) {
                    q.push(j); st[j] = true;
                }
            }
        }
    }
    if (dist[n] == INF) return INF;
    return dist[n];
}

本题正解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int main() {
    /*
        ...
        v 数组有必要存的只有 v[a], v[b]
        所以用 va, vb 两个 int 替代
    */
    queue<int> q;
    q.push(a);
    st[a] = 1;
    dist[a] = 0;
    while (!q.empty()) {
        int t = q.front(); q.pop();
        if (t == b) {
            break;
        }
        for (int i = h[t]; ~i; i = ne[i]) {
            int j = e[i];
            if (dist[j] > dist[t] + 1) {
                dist[j] = dist[t] + 1;
                q.push(j);
            }
        }
    }
    if (dist[b] != INF) {
        cout << vb - va + dist[b] << endl;
    } else {
        cout << "No solution";
    }
    return 0;
}

数据量有点迷惑性,$10^5$ 对应的时间复杂度 $O(n\log n)$ 让人联想到 Dijkstra 等算法,然后发现实际上是一个 $O(n)$ 的 BFS。

此题难度不高,出题组第一次出七级的题可能把握不住题目难度,不作七级题目难度参考。

纸牌游戏 202312 B

洛谷题库 P10111

题解

经典最值问题,有非常浓厚的状态转移风味,并且无后效性,考虑 DP。

  • 状态表示 f[i][j][k] 表示在第 $i$ 轮出了牌 $j$、累计换牌(额外扣分) $k$ 次时可以获得的最高分数。
  • 状态计算 f[i][j][k] = max(f[i - 1][t][k + ((j == t) ? 0 : -1)]),其中 $t \in \lbrace 0, 1, 2 \rbrace$。

最后取所有 $j \in \lbrace 0, 1, 2 \rbrace , 0 \leq k \le n$ 的 f[n][j][k] - b[k] 的最大值(b[k] 为额外扣分数组的前缀和)。

注:可以把 f 数组的第一维压掉,但不差这点空间。

感受

做代码优化时,别的不用考虑,只要保证做的是等价变形。——yxc

DP 的状态表示基本靠硬想,唯一的技巧就是题里有啥就写啥,想到什么表示方法就试试,不行的话就尝试增加或减少一个维度之类的。

偶尔做题时会发现一些有意思的小事情,比如每轮得分可以表示为 ((c[i] == j) ? a[i] : (2 * ((c[i] + 3 - j) % 3 - 1) * a[i]))c[i] 为对手出牌,j 为我方出牌,a[i] 为本局分数),然后你就会发现这玩意完全没用

如果状态转移想的不太清楚的话,会产生大量复制粘贴,最终码量在 2KB 左右,找规律可以减少一些复制粘贴,可以降到 1KB 码量。

本题难度中等,状态表示很难,算是一道优秀的 DP 题。

交流问题 202403 A

洛谷题库 P10378

题解

A 校和 B 校很明显是一个二分图的两部分。每个连通块都有两种分学校的方案,可以通过染色法分出学校,左右两半分别是 A B 校或 B A 校,两种方案产生了两组“该连通块中 A 校人数和 B 校人数”,记录两组中较小的 B 校人数(其实 B 校还是 A 校没区别,所以也可以只求一组,取两校人数的最小值)。仅需计算该二分图所有的连通块的“较小的 B 校人数”之和,该数是 B 校总人数的最小值,用 $n$ 减去该数就是 B 校总人数的最大值(即 A B 校学生数互换)。

感受

二分图明显就明显在所有边(交流)都在两学校之间,学校内不连边。每个连通块内的集合(学校)划分就是给每个点(学生)打标签,与染色法十分相似。

很水,感觉思考深度和前两题完全不是一个级别(话好像说反了)。

俄罗斯方块 202403 B

洛谷题库 P10379

题解

用类似于 floodfill 的算法解决,固定从每个俄罗斯方块左上角开始搜索,这样就可以使每个方块拥有一个固定的有根搜索树,相同形状的俄罗斯方块搜索树也相同,把遍历顺序(层序遍历,树上 DFS 等)当作这个类型的俄罗斯方块独一无二的标识。

set 的去重功能统计有多少种遍历顺序,即有多少种俄罗斯方块。

感受

floodfill 的特征还算明显,难就难在如何表示每个俄罗斯方块。这么做我是完全没想到的,看别人的题解看到的。遍历顺序是一个搜索的过程中就产生的东西,不增加复杂度,可能是一个比较容易发现的算法,但是我没发现。

巧妙的搜索题,难度的话,中等?

黑白翻转 202406 A

洛谷题库 P10723

题解

我们发现,只有连接两个及以上黑色节点的白色节点才需要染黑。白色叶节点并不能连接黑色节点,没有必要染成白色,所以循环删除所有白色叶节点。最后统计剩下的白色节点数并输出,因为最后剩下的白色节点都是需要染成黑色的。

注:无根树的叶节点的定义是度数不大于 $1$ 的点。

感受

难度中等。

思路就是连接黑色节点,其余白色全部删除。容易走到歪路上,像是 DFS 之类(有可能路没歪但是我想不明白)。

区间乘积 202406 B

洛谷题库 P10724

题解

容易发现:一个数是完全平方数时,它的所有质因子的指数都为偶数。

在质因数分解的过程中,我们可以通过状态压缩来存储每个质因子的指数的奇偶性,每一个二进制位代表一个质因子,这一位为 $1$ 的含义是其对应的质因子的指数是偶数,反之亦然。判断区间乘积是否为完全平方数可以通过该区间质因数分解结果的异或和是否为 $0$ 来判断。

建立 f 数组,记录区间异或和出现次数,答案就是重复区间异或和出现的次数。

感受

难度简单。

当我们发现要记录奇偶性,又是加起来的时候,就可以想到异或以及状态压缩。

重复区间异或和出现次数这一点比较难想。

总结

整体难度略高,不稳定。

考的算法是搜索、图论、DP、数学 这几类,其中 DP 和数学很难。

Built with Hugo
主题 StackJimmy 设计