AtCoder Beginner Contest 248 Solution

更好的阅读体验戳此进入

题面链接

题面 Luogu 链接

[ABC248A] Lacked Number

题面

给定字符集为 0-9 数字的字符串,求字符集中没有在字符串中出现的唯一数字。

Solution

语法题。

Code

[ABC248B] Slimes

题面

给定 a,b,k,求最小的 x 使得 a×kxb,输出 x

Solution

语法题,数据范围不大所以直接浮点数搞一下 log 即可。

Code

[ABC248C] Dice Sum

题面

给定 n,m,k,求满足以下条件的长度为 n 的序列数,对 998244353 取模。

Solution

数据范围太小,写一个 3D1D 的 dp 就行,没啥可说的。

Code

[ABC248D] Range Count Query

题面

给定序列 AnQ 次询问求 [l,r]ai=x 的数量。

Solution

值域较小,每个数维护一个出现位置的 basic_string,每次在里面二分两次查左右端点调用一下 distance() 即可。

Code

[ABC248E] K-colinear Line

题面

给一堆点问有多少条直线至少经过了 k 个点。

Solution

数据范围较小,随便枚举一下然后判个重即可。

Code

[ABC248F] Keep Connect

题面

给定 n,p,存在如图的 2×n 的网格图,显然初始共有 2n 个顶点和 3n2 条边,分别求删除 i[1,n1] 条边后仍使图连通的删边方案数,对 p 取模。

Solution

这种题 DP 很显然,考虑设状态 dp(i,j,0/1) 表示考虑前 i 列,删除了 j 条边,第 i 列上下两点之间是否连边,然后对不同情况无脑进行分类讨论即可。

具体地,对于 dp(i,j,0),如下图,此时 i 位置两个点竖直方向并未连边,则为了保证连通性,那么 ii+1 的水平的两个边必须连上,而 i+1 的竖直的边(即虚线边)是否连结均合法,则有如下转移:

dp(i,j,0)dp(i+1,j+1,0)dp(i,j,0)dp(i+1,j,1)

Oops! The image is blocked! Please visit my own website to observe the image! 图片被墙了,请通过文章头的跳转链接访问!

对于 dp(i,j,1),如下图,三条边都不能直接确定,所以需要继续讨论,具体地,可以讨论 i+1 的竖直边是否连结,简单想一下就有如下 4 个转移,此处不多赘述,直接看方程吧。

dp(i,j,1)×2dp(i+1,j+1,1)dp(i,j,1)dp(i+1,j,1)dp(i,j,1)×2dp(i+1,j+2,0)dp(i,j,1)dp(i+1,j+1,1)

同时注意 ×2 是因为要枚举上下的两个水平边删掉其中一个。

Oops! The image is blocked! Please visit my own website to observe the image! 图片被墙了,请通过文章头的跳转链接访问!

边界可以是 dp(1,0,1)=dp(1,1,0)=1,对应删边数 d 的答案即为 dp(n,d,1)

Code

[ABC248G] GCD cost on the tree

题面

给定一颗树有 n 个结点,每个结点上有一个权值 ai, 对于每条至少包含两个点简单路径,它的贡献为 路径上点的数量(包括端点)× 路径上所有点的 ai 的最大公约数(gcd)。求所有简单路径的贡献之和,对 998244353 取模。

Solution

考虑枚举每个子树,无脑搜索所有路径,用 map 存储对于每个路径上的 gcd 作为第一关键字对应的路径长度和和对应的路径数量,存储后者主要为了在路径延申的时候加上对应的数量,然后一个子树完全算完之后往回递归,把这些全都合在一起,整棵树 dfs 一遍即可,最终复杂度 O(σ2(ai)nlogn)。也可以用容斥写,但是会更难写一些。

Code

[ABC248Ex] Beautiful Subsequences

题面

给定排列 Pn 和整数 k,求满足如下条件的点对 (l,r) 数量。

Solution

首先为了方便我们令 mx=maxi=lrPi,mn=mini=lrPi,原式转化为 mxmnrl+k,发现 k[0,3],所以可以考虑将不等式转化为等式,枚举 k,对于每个 k,合法方案满足 mxmn=rl+k。于是原式可以转化为对于每个 r[1,n],求满足 l[1,r],k[0,K],l+mxmn=r+k 的个数,此时 l,r,k 均为固定的,所以我们只需要求出此时的区间 max,min,即可确定是否合法。换句话说,对于枚举的 r,要找其左侧所有点的 l+mxmn 的值与 r+k 相等的个数。

于是到此问题可转化为,求区间最大值最小值,然后再求区间等于 r 的数量。对于求区间最大值最小值,这个东西我们发现求的是对于每个 r 的所有后缀最大最小值,这个东西用单调栈维护即可。具体地,对于一个新的值,显然其只有可能更新它前面的点的最值,其自己在第一次插入的时候后缀的区间为 [i,i],最值显然均为 ai,但是这个时候我们却不需要更新最值,因为我们要的值是 mxmn,既然最值相同那么这个东西为 0,所以可以忽略。然后对于一个新的值,要去尝试更新前面值的最值,如对于最大值需要维护单调递减的栈,即栈顶最小,如果新的值更小直接丢到栈上,否则更新栈顶和次栈顶之间区间的最大值。把这些维护完之后直接求所有值为 r 的点即可。

然后考虑如何维护区间等于 r+k 的个数,有一个比较好想的就是维护一棵线段树,对于每个节点都维护一个 basic_string < pair < int, int > >,存储对应值有多少个,合并的时候直接把两个加起来即可,然后去个重。同时此时查询的时候可以不用枚举 k,直接找 r+k 大于等于对应值即可。以此我们便可很直观地写出如下实现,但是这个是错误的。

Code

不过这个东西我们简单分析以下复杂度就会发现,每次合并和修改之后的 Pushup 都会使其重构然后耗费大量时间,所以最后复杂度是 O(玄学) 的,提交后就会发现 AT 上只能过十多个点。。感觉复杂度直接退化到暴力了。

然后对于 basic_string < pair < int, int > > 还有个很严重的问题,对于一般的 C++14 及以下都不会有任何问题,但是在 C++17 之后,因为 basic_string.h 中有如下语段:

然而引入的这个头文件中还存在如下语句:

此时我们发现,这东西会 CE!测试后发现如下语段会输出 0

众所周知 is_trivial 一般就是用于判断类型的构造函数是否为默认构造函数,而 pair 的构造函数似乎是用初始化列表写的,可能是因为这个原因,就会导致其无法通过这个 assert,于是就寄了。然而 AT 上默认的不知道是 C++17 还是 20 或者更高,所以无法过编。这个或许可以通过一些高妙的方式解决,但是我不会,于是考虑自定义一个结构体实现跟 pair 一样但是使用默认构造函数即可。

所以话说回来,这个做法本身就是错误的,于是现在我们考虑正解:

思考什么东西比较好维护区间最值和区间等于 x 的数量,不难想到分块,维护的方式和上面说的差不多,只是用分块替换线段树即可,这个东西的复杂度就是 O(knn),可以通过,似乎也是官方正解,但是我不太喜欢分块这个复杂度不够优秀,所以这里就不给代码实现了,我们考虑一些更优秀的做法。

考虑分治,思路来自机房大佬 @sssmzy,发现对于每一个区间,如果我们令 l[L,mid],r[mid+1,R] 是可以快速维护答案的,于是我们只需要以此为基础,计算完答案后分别二分下去即可。

具体地,对于维护答案的过程,我们发现最大值和最小值的位置无法确定,所以考虑枚举最大值在左侧或右侧,以左侧为例子,我们按照类似猫树的思想,从 mid 向两侧维护后/前缀最值,不难想到,以前缀最大值为例,显然扩展时最大值是单调不降的,最小值同理,然而我们需要让最大值取在左侧,那么显然存在一个分割点 sp1,在其左的右侧点是合法的,同时也存在一个分割点 sp2,满足在其左的点最小值取左侧,其右的取右侧,然后发现这个东西是单调的,所以可以直接双(三)指针,枚举 l,考虑 sp1,sp2,同时维护桶记录所有可能的合法的值的数量。然后讨论 r 位置与分割点的关系,如果在 [mid+1,min(sp1,sp2)] 那么显然此时最大最小值都在左侧,也就是此时确定 k 之后直接就有 r=l+mxmnk,判断 RHS 是否在范围内即可。否则在 (sp1,sp2] 之间的话,最大值在左侧和 l 相关,最小值在右侧和 r 相关,此时有 rmn=l+mxk,同样将对应 RHS 的桶加上即可。然后再考虑 mx 再右侧同理算一遍即可,注意此时因为索引可能为负,所以需要加个 n 转正。

当前区间算完之后二分下去分别求解即可,这样最终复杂度 O(knlogn),跑得飞快。

UPD

update-2022_11_22 初稿