第十三届蓝桥杯大赛软件赛决赛 C/C++ 大学 A 组 部分题解
第十三届蓝桥杯大赛软件赛决赛 C/C++ 大学 A 组 部分题解
A. 小蓝与钥匙
求 \(28\) 个数随机排列后,有 \(14\) 个正确归位,其他的 \(14\) 个恰好都不在位置上的方案数。
很显然的错排问题,令 \(\displaystyle D_n=\lfloor{n!\over e}\rfloor=\sum_{i=0}^n{n!\over i!}\cdot (-1)^i\) 表示 \(n\) 个数错排的方案数。
答案显然为 \(\displaystyle \dbinom 28 {14, 14}\cdot D_{14}=1286583532342313400\) ,只能说答案确实是 long long 范围内的。
B. 排列距离
给定两个长为 \(17\) 的排列,每次可以将一个排列变成 pre_permutation 或 next_permutation ,问第一个排列变成第二个的最少步数。
分别经典的康托展开问题。由于 \(17!<2^{63}\) ,直接对两个排列康托展开得到,分别为 \(pa=4542892071974, pb=254081962595831\) 。
两个排列之间的最少步数显然要么直接从 \(pa\) 往 \(pb\) 走,要么绕过 \(17!\to1\) 往 \(pb\) 走,答案为 \(\min(|pa-pb|, 17!-|pa-pb|)=106148357572143\) 。
C. 内存空间
编译原理
给定变量的申请式,求内存开销。
照题意模拟。
D. 最大公约数
给定长度为 \(n\) 的数组 \(a\) 。每次操作可以选择数组中相邻的两个元素 \(x, y\) ,将其中一个替换为 \(\gcd(x, y)\) 。问将数组变换为全 \(1\) 的最少步。
不确定做法对不对。
显然,若 \(\gcd(\{a\})>1\) 则无解。
当 \(\exist i\wedge a_i=1\) 时,我每次选择一个与 \(1\) 相邻的非 \(1\) 元素,将其变化为 \(1\) ,操作步数为 \(n-cnt_1\) 。
对于剩下的情况,问题在于如何用最少的步数拼出一个 \(1\) ,后续就是同第二个情况,操作步数为 \(n-1\) 。
由于每次操作都是选择相邻的元素,一个想法是我们先将 \(a_2\) 变为 \(\gcd(a_1, a_2)\) ,再将 \(a_3\) 变为 \(\gcd(a'_2, a_3)\) ,一直到 \(a_n\) 变为 \(\gcd(a'_{n-1}, a_n)=\gcd(\{a\})\) 。这样的操作能保证最后时刻,\(a_n=1\) 。
然而,这样不能保证步数最少性。类比而来,我们可以对一个区间 \([l, r]\) 采用上述操作,那么即可在 \(a_r\) 处得到 \(\displaystyle \gcd_{i=l}^r a_i\) ,步数开销为 \(r-l\) 。
很显然,若我们找到某个区间使得 \(\gcd\) 为 \(1\) ,则操作步数显然可以为 \(n-1+(r-l)\) ,显然只和区间长度有关。于是根据 \(\gcd\) 的单调性,我们可以二分区间长度,去验证是否存在 \(\gcd\) 为 \(1\) 的区间。
问题在于需要多次查询某个区段的 \(\gcd\) 结果。我们根据 \(\displaystyle \gcd(\gcd_{i=l_1}^{r_1} a_i, \gcd_{i=l_2}^{r_2} a_i)=\gcd_{i=l_1}^{r_2} a_i(r_1\geq l_2-1)\) ,很显然这个东西类似 \(\max,\min\)(实际上就是 \(\min\)),可以由 ST 表维护。
建立 ST 表复杂度为 \(O(n\log n\cdot \log n)\),二分答案为 \(O(\log n)\),验证区间存在性复杂度为 \(O(n\log n)\) ,因此总复杂度为 \(O(n\log^2 n+\log n\cdot n\log n)=O(n\log^2 n)\) 。
当时考场上石乐志,没想到 ST 表,写了个线段树,复杂度退化为 \(O(n\log^3 n)\) 。只能祈祷我的线段树常数足够优秀(不是)。
E. owo
给定 \(n\) 个字符串,对于前 \(i\) 个字符串,输出其拼接后,owo 子串出现次数的最大值。
很显然,一个字符串内部的 owo 出现次数并没有拼接意义,直接统计入答案即可。而根据拼接意义,字符串可以简单分为以下 \(10\) 类:
不提供后缀 | 提供长度为 \(1\) 的后缀 | 提供长度为 \(2\) 的后缀 | 其他 | |
---|---|---|---|---|
不提供前缀 | x-x(0) | o-x(3) | wo-x(6) | |
提供长度为 \(1\) 的前缀 | x-o(1) | o-o(4) | wo-o(7) | |
提供长度为 \(2\) 的前缀 | x-wo(2) | o-wo(5) | wo-ow(8) | |
其他 | w(9) |
然后就开 \(10\) 个变量分别统计一下每一种的数量,对于拼接新产生的串贪心一下它们的贡献。
但是我不确定自己的贪心策略是否正确,这里就不继续讨论。
F. 环境治理
给定 \(n\) 个点的完全图,每一条边有初始权值 \(D_{i, j}\) 和最小权值 \(L_{i, j}\) 。定义 \(P=\sum_i \sum_j dis(i, j)\) 为图上任意有序两点的最短路和。现在按标号循环顺序,每天选择一个点,将其所有大于最小边权的边权减 \(1\) 。求使得给定值 \(Q\geq P\) 的最小时间。
我们先验证在所有边取最小边权的情况下,条件是否成立,不成立则无解。
注意到每 \(n\) 天,整个图的周期是一轮的。而一轮中,根据握手定理,每条边的边权都减少 \(2\) 。
我们不妨二分使得条件成立的最少周期 \(T\) 。那么,所有的边权变为 \(\max(D_{i, j}-2T, L_{i, j})\) ,\(P\) 的求解直接全源最短路即可。
二分得到该周期后,可以确定,该条件的最小时间位于区间 \([T-n+1, T]\) 中,暴力验证即可。
每次求解全源最短路的复杂度为 \(T_1(n)\) ,则次数为二分的 \(O(\log \max|D-L|)\) 和暴力验证的 \(O(n)\) 求和,故总复杂度为 \(O(n)\cdot T_1(n)\) 。
如果采用 Floyd ,本题的复杂度是 \(O(n^4)\) 刚刚好。奈何本人复杂度算错了,用了 \(dijkstra\) ,复杂度为 \(T_1(n)=O(nE\log E)=O(n^3\log n)\) ,最后答案多了个 \(O(\log n)\) 。
G. 选素数
对于一个给定的数 \(x\) ,可以选定一个小于 \(x\) 的质数 \(p\) ,使得 \(x\) 变为 \(\displaystyle \lceil{x\over p}\rceil\cdot p\) 。现在已知变化两次后,结果为 \(n\) ,求最小的 \(x\) 。
我们不妨假设一次操作后,得到的结果为 \(y\) ,那么有 \(\displaystyle y=\lceil{x\over p}\rceil\cdot p\) ,显然有 \(p\mid y\) 。
变换一下,我们得到 \(\displaystyle {y\over p}=\lceil{x\over p}\rceil \Leftrightarrow {x\over p}\leq {y\over p}<{x\over p}+1\) 。
因此有 \(y-p<x\leq y\) 即 \(y-p+1\leq x\leq y\) 。
因此固定的 \(y, p\) ,\(x\) 的最小值即为 \(y-p+1\) 。
因此固定的 \(y\) ,\(x\) 的最小值取决于 \(y\) 的最大质因数 \(P_y\) ,且为 \(y-P_y+1\) 。
我们可以对所有的 \(y\) ,建立它对应的最小的 \(x\) 的映射表。复杂度为 \(\displaystyle \sum_{i=1}^\infty {n\over p_i}=O(n\log\log n)\) 。
而 \(y\) 与 \(n\) 同样具有关系,在 \(n, p\) 固定时,\(n-p+1\leq y\leq n\) 。因此直接查表 \([n-P_n+1, n]\) 的最小值即可。
若没想到只需要取 \(n\) 的最大质因数(比如赛时的我),暴力将 \(n\) 进行质因数分解,对 \(y\) 的映射表建立 ST 表,然后在 ST 表上查,也是可以的。复杂度为 \(O(n\log \log n)+O(\log n)+O(\sqrt n)\cdot O(1)=O(n\log n)\) 。
H. 替换字符
给定字符串 \(s\) ,和 \(n\) 次操作。每次操作将区间 \([l_i, r_i]\) 的所有 \(x_i\) 字符换为 \(y_i\) 。求最后的字符串。
不会正解。
维护一个 \([1, n]\) 区间的线段树,每个区间维护 \(trans[i]\) 表示 \(i\) 个字母具体映射到第几个字母,初始设定全部映射到自己,即 \(trans[i]=i\)。
下推的时候,若父亲的 \(trans[i]=x\) ,则遍历孩子的所有字母,查询 \(trans\) 值为 \(i\) 的字母,全部修改为 \(x\) 。每次询问最多下推 \(O(\log n)\) 个结点,每次下推的复杂度为 \(O(|\Sigma|^2)\) 。
最后求答案的时候,强制将树上的所有 \(trans\) 下推。下推的结点数位 \(O(n)\) 的,推到叶子了输出一下对应字符。
总复杂度为 \(O(m\log n+n)\cdot O(|\Sigma|^2)=O(m\log n\cdot |\Sigma|^2)\) 。多了一个数量级,弱一些的数据能过。
应该可以通过维护 \(trans\) 的反向映射来将下推优化到 \(O(|\Sigma|)\) 的,但没时间了,每细想。
I. 三角序列
给定 \(n\) 个三角形,每个三角形有两个参数 \(a_i, b_i\) 。\(a_i\) 表示三角形边长,\(b_i\) 表示三角形是否是递减的。有 \(m\) 次询问,每次询问坐标 \(l_i, r_i\) 的三角形,对于给定数值 \(v_i\) ,需要找到最小的 \(h\) 使得高度不超过 \(h\) 的整点数大于等于 \(v_i\) 。
不会正解。
对于高度的寻找,我们可以去二分一个高度,验证答案是否可行。我们需要统计范围内的三角形组合,在不超过该高度的地方的点数。
显然,三角形组合的情况分为两种,第一种是 \(l_i, r_i\) 分布在一个三角形上,直接暴力统计 \(O(\max a_i)\) ,或推导一下公式,可以 \(O(1)\) 算结果。
第二种是只有最左边的三角形和最右边的三角形可能不完整。为了描述的方便性,我们规定最左和最右的均不完整。
对于中间的三角形,我们可以以三角形为单位,暴力跑过去(记得高度不超过二分的值),答案为 \(O(n)\) 。对于左右的不完整的三角形,我们可以分类讨论,然后 \(O(\max a_i)\) 暴力统计或 \(O(1)\) 算公式。
复杂度为 \(O(nm\log a_i)\) ,常数比较小的应该能冲过 \(50\%\) 的数据。
J. 括号序列树
<没有看懂。不然出题人你自己来读一读你的题目是大家读得懂的吗?>