2020校队预选赛A-字符矩阵-题解
关于本次比赛
首先道个歉,卡掉了时空复杂度为 $n^3$ 的 $dp$ 做法确实是不应该,零基础的话能自学到掌握基本 $dp$ 的程度应该是很优秀的水平了。
而本次组题问题也很多,包括没有一个总的命题人来把握整体难度 和 “祖安” 一题出现意料外的解法(正则表达式)等。再次对我们命题工作的不当导致大家不好的比赛体验道个歉。
但每个题所涉及的知识点多为算法竞赛中实用的算法或技巧,数据也经过多次验证,欢迎大家赛后补题。
题目来源
Div.1 #517 B 的简化版本
题解
回到本题,题目大意为给你一个 $n*n$ 大小的矩阵,每个位置上有一个小写字母,从 $(1,1)$ 开始每次向右或向下移动到达 $(n,n)$ 并按顺序取走路径上的字符来形成一个字符串,请你求出其中字典序最小的解。
O(n^3)
考虑使用动态规划,$f[i][j]$ 表示从 $(1,1)$ 出发到 $(i,j)$ 所能形成的字典序最小的字符串,转移方程为:
其中 $s[i][j]$ 表示矩阵 $(i,j)$ 位置的字符,$”+”$ 表示字符串的拼接。
这个 $dp$ 看起来状态只有 $n^2$,但转移实际上是 $O(n)$ 的,因为字符串字典序的比较(包括std::string)是 $O(n)$ 的,因此总体时间复杂度为 $O(n^3)$,$n=2000$ 时计算次数约为 $8e9$,而较快的评测机大概 5s 可以跑 $1e9$,无法通过本题,而且空间复杂度也为 $O(n^3)$,交上去会 $MLE$。
1 | int n; |
O(n^2)
考虑一个朴素的贪心算法,由于字典序比较的特殊性,我们需要让靠前的字母尽量的小,因此从 $(1,1)$ 出发每次选择较小的字符进行转移。
但考虑
bba
bbb
bbb
的情况,我们无法确定从 $(1,1)$ 开始下一步应该转移到哪里,如果使用 $dfs$ 的话复杂度也是 $n!$ 级别。
但实际上对于这种无法确定的转移一定是在同一对角线上的(对应的字符串长度相同),我们可以去重后全部保留,这种转移数总级别是 $O(n^2)$ 级别的,考虑到扩展一个字符是 $O(1)$的,因此我们可以得到一个 $O(n^2)$ 的算法:
顺序考虑字符串第 $i$ 位的字符 $(1<=i<=2*n-1)$:
1. 建立一个待扩展队列,初始时只有 (1,1)
2. 查找待扩展队列中最小的字符,记 c,同时为答案末尾添加上 c
3. 保留队列中 s[i][j]==c 的 (i,j)
4. 对于保留下来的 (i,j),添加 (i+1,j) 和 (i,j+1) 到新扩展队列中
5. 将旧扩展队列替换为新扩展队列
6. 若没到达 (n,n) 从第二步开始重复
实际写法类似于 $bfs$,时空复杂度均为 $O(n^2)$ 可以通过本题。
1 | int n; |
2020校队预选赛A-字符矩阵-题解