【题解】2023牛客寒假算法基础集训营2
题解写的比较匆忙,有误请指出。
2023.01.19 UPD:更新E题证明与三分反例,感谢这几位群友,以及提出疑问的群友:恶魔妹妹。
2023.01.25 UPD:更新一个牛客能跑4e10的证据。
这个提交在polygon不管用哪个版本的cpp都是TLE的,后台有n,q都大于1e5的数据。提交中的a数组把bool改成int会TLE,猜测是牛客神机把bool当成bitset,复杂度是nq/64,大概6e8,由于for里只有一个赋值语句,常数极小,所以跑的飞快。 https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60516804
难度情况
出题时预估的难度:
- easy: AB
- easy-medium: DHJ
- medium: CEFIL
- hard: GK
内测&正赛:
- easy: A
- easy-medium: BDJ
- medium: CFHL
- hard: EGIK
内测后,通过率 (过题人数/有提交人数:3920) 预测情况: 可以看到与预想的还是有些差距(
题目花絮
-
BC是以前存的idea,然后兰子觉得B不够签,所以加了A。
-
D是临时想的,想出一道跟树和贪心有关的题,用来补足这套题没有贪心的缺陷,但想不出比较有趣的题。
-
E是以前存的idea,本来是求 到 中的那个 最小是多少,后来觉得太简单了,加强了一下变成了这道题。最开始我也以为是个三分板子题,然后自己写了一个整数三分发现整数三分不能做这道题。我之前也不太熟悉三分,属于是自己出了个题把自己教育到了。
-
FG本来准备丢上Codeforces Round #789并分成easy,hard两个版本。首先被审题人说觉得F这个版本太简单了没什么意义,于是easy版本被毙了;hard版本进入了test阶段,然后被一个波兰final选手说这个题是一眼题,但写起来是"巨大的痛苦",然后我说"代码能力也是算法竞赛的一部分",但最后还是被毙了,于是就放到了寒假营里。
-
H也本来准备丢CF当div2的B题(当时的版本是输入 ,而不是现在 ),后来在赛前几场有一个题长的跟这题有点像,就换了(只是有点像,并不是原题),然后放到了寒假营里。放进来的时候觉得太简单了,于是就加强了一下(
-
I也本来准备丢CF当div2的C题,但审题人觉得没什么意思,就毙了。
-
J是以前做题遇到的一个子问题,结论挺简单的,就拿来当easy了。
-
K是根据一个工作内容演变出的idea(虽然最后没有应用到项目中)。本来想丢CF当div1的C题,后来出题组里的OI爷发现它的做法很像数三元环,就毙了。
-
L本来打算出成dp,后来发现容斥也能做,而且比dp做法简单,难度--
A. Tokitsukaze and a+b=n (easy)
签到题。 枚举 = 到 ,,再判断 是否在区间 中,如果是,答案 。
代码:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60499571
B. Tokitsukaze and a+b=n (medium)
根据 的取值范围 ,我们求出能满足 的 的范围是 ,但是合法的 的范围是 。所以答案是区间 与 取交后的区间长度。
区间取交的区间长度可以这么计算: 两个区间 , 取交的区间长度为:
代码:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60499713
C. Tokitsukaze and a+b=n (hard)
同样的,可以根据 的范围 能算出 的范围 ,再与合法的 的范围取交即可。由于这题变成了 个区间,那么合法的 的范围总共有 个(因为有 个已经用来当成 了)。但遍历每一个 的合法范围时间复杂度是 的,需要进行优化。
设 表示 时的区间个数。由于区间范围是 级别,我们可以将 个区间利用 差分前缀和 计算出 数组。再做一次前缀和计算出 数组。那么 的范围是 时,可能的 的数量可以用通过 计算出:。但是要注意这样会把 与 相交的长度算进去,所以要减去。
计算时要记得取模。由于存在减法运算,要注意最后答案不要变成负数。
时间复杂度
PS:本题还可以使用 树状数组/线段树 等数据结构进行区间求和。
代码:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60499758
D. Tokitsukaze and Energy Tree
可以发现当第 个能量球放置在节点 时,它的贡献为:节点 到节点 的高度 能量球的能量
于是我们可以贪心。求出高度 后,分别对高度 和 能量 排序,之后大的乘大的,再全部加起来即可。
代码:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60499894
E. Tokitsukaze and Function
通过暴力打表 或者 对 求导后,发现在 处 最小。但不确定是在 处还是在 处。
我们可以分别求出 , , , 进行排序,求出最小的 。
令 为最小的 ,现在要求最小的 。显然在 内的 是单调递减的,所以我们可以在 范围内二分求出 。时间复杂度
另外需要注意的是,本题无法使用三分解决,因为三分要求在极小值左边和右边都是严格单调的,而由于本题的 函数有取整运算,可能使得函数的一些部分不是严格单调的,使用三分有可能找不到极小值点。
代码:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60499905
补充为什么直接三分不行:
补充两位群友的证明:
群友1:
群友2:
F. Tokitsukaze and Gold Coins (easy)
显然,走进怪物格子一定不是最优解,所以我们把有怪物的格子当成障碍物来看。所以题目转化为:求 到 的所有路径能覆盖的格子数量。
我们可以通过两次 DFS/BFS 来搜索答案:
- 从起点 开始,向下/右走,标记所有能到达的格子。
- 从终点 开始,向上/左走,标记所有能到达的格子。
在这之后,只要统计哪些格子被标记过两次即可。
时间复杂度 。
代码:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60499913
G. Tokitsukaze and Gold Coins (hard)
显然,走进怪物格子一定不是最优解,所以我们把有怪物的格子当成障碍物来看。所以题目转化为:求 到 的所有路径能覆盖的格子数量。
我们可以通过第一列和第三列来确定第二列的可行区域 。
首先找到第一列向下的第一个障碍物,坐标为 。接着,我们通过 来寻找可行区域的最大值 。我们在第二列找到第一个大于等于 的障碍物,坐标为 ,然后找到小于 的第一个空白处,坐标为 。
同理,我们先找到第三列向上的第一个障碍物,坐标为 。接着我们通过 来寻找可行区域的最小值 。我们在第二列找到第一个大于等于 的空白处,坐标为 ,然后找到小于 的第一个障碍物,坐标为 。
可以用set分别维护障碍物和空白处,完成上述操作。
记 , 。
现在第一列的可行区域为 ;
第二列的可行区域为 ,
第三列的可行区域为 。
如果 或者 或者 ,则无法从 走到 ,所以答案是 。否则答案为
表示第二列 行到 行的障碍物个数,这个可以使用树状数组来维护第二列的障碍物得到。
总时间复杂度 ,常数较大。
代码:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60499923
H. Tokitsukaze and K-Sequence
可以发现每种数字的贡献可以分开计算。我们按照每种数字出现的次数进行讨论。
假设数字 出现的次数为 :
- 如果 ,我们可以贪心地将每个 都分到某个子序列中,使得每个子序列要么只包含 个 ,要么不包含 。所以此时数字 的贡献为
- 如果 ,我们按照上面的方法分配完 个 ,多出来的 必须分配到某个子序列中,导致那个子序列中,数字 没有贡献。所以此时数字 的贡献为
答案就是每种数字的贡献求和。
现在题目求 的答案。我们对 从小到大排序,枚举 ,答案为 的 求和,加上 的 的个数乘上
时间复杂度
代码:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60499927
I. Tokitsukaze and Musynx
先对 排序。总共有 个区间,枚举每个 在哪个区间,二分出每个区间内包含几个 ,再乘上对应区间的 ,求和,即是当前情况的答案。最后对所有情况的答案取 max 即可。
具体的,我们可以枚举每个 在哪个分界点上,来计算答案。实现的时候为了方便,求出当前枚举的分界点与第一个分界点的差值 ,将所有 平移 即可。
时间复杂度
代码:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60500147
J. Tokitsukaze and Sum of MxAb
看到带有绝对值的式子,一般先展开绝对值。我们对 和 的正负进行讨论:
- , 时,
- , 时,
- , 时,
- , 时,
所以
那么原式
代码:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60500156
K. Tokitsukaze and Synthesis and Traits
题意转化为:给一张可能不连通的无向图, 次查询,每次给出 个点,查询 条边中,有多少条边在原图中出现过。
Solution 1:给边定向
考虑给原图的边定向。设 表示 节点 的度,则对于原图中的一条边 , 若 , 则连接 ,否则连接 。给原图的边定向后,每个点的出边 。对于每次查询,用桶记录每个点,然后直接遍历每个查询的点的所有出边即可,常数较小。总时间复杂度为 。
证明:对于一个查询的点 ,设 表示 的出边数量。 若 ,则显然遍历所有出边的时间复杂度小于 ;若 ,由于 ,则总边数最多为 ,所以 不超过 。
PS:如果看不太懂,画个图就明白了。
代码:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60500250
Solution 2:根号分治
对每组查询的点数按照 进行分类:
-
若 ,则暴力枚举 条边,用 hash 判定是否在原图中出现。
暴力的时间复杂度为 ,这一情况出现的次数为 ,因此总时间复杂度不超过 -
若 ,则暴力枚举所有查询点的出边。
暴力的时间复杂度最坏为 ,这一情况出现的最大次数为 ,因此总时间复杂度不超过 。
由于 与 的数量级一致,所以两种做法的时间复杂度区别不大,但是根号分治需要多一步 hash ,常数较大。
如果你是手写hash,那么是可以过的:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60500261
如果你是用gp_hash_table,那么也是可以过的:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60500349
但如果你是用unordered_map,那么残念,会TLE(也可能是我写臭了):https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60500358
L. Tokitsukaze and Three Integers
Solution 1:容斥
表示 的个数,可以在 的时间复杂度内计算出来。
接下来枚举 ,计算 满足 ,时间复杂度为 。
但这样会把 或者 的情况计算进答案,我们需要把这部分减掉。枚举 的情况,减去 满足 , 的情况同理。时间复杂度为 。
总时间复杂度为 。
代码:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60500430
Solution 2:dp
我们让 有序,分两种情况:
- 在左边或者右边,即 或者
- 在中间,即
由于 ,所以不需要管 和 的顺序,最后答案乘 即可。
-
对于情况1,只需要正着做一遍dp,倒着做一遍dp即可:
表示前 个数,已经选了 ,且 的方案数;
表示前 个数,已经选了 和 ,且 的方案数;
初始化 ,表示选 作为
转移:
+= ,表示不选 += ,表示不选 += ,表示选 作为 += ,表示选 作为
发现 的第一维 只跟 有关,可以用滚动数组优化掉这一维。 -
对于情况2,我们依然枚举 , 表示合法的 的数量。
先将 , ,, 更新进 。
枚举合法的 ,从 到 。假设当前 :
把 作为 的不合法贡献从 中删去 选 作为 计入答案: += 把 作为 的合法贡献加入
总时间复杂度同样是
代码:https://ac.nowcoder.com/acm/contest/view-submission?submissionId=60500437