CF、AT 杂题题解
CF1455F
solution 1
前 次操作只会影响到 ,并且在第 次操作前,原本在位置 的数只可能在 或 。
于是就可以考虑设 表示完成了前 次操作,原本在位置 的数现在在位置 时 的最小字典序,每种操作模拟一下即可。
状态数为 ,每个状态为一个 的string
,于是时间复杂度为 ,能过。(优点:好写)
solution 2
进一步分析,第 次操作只会影响到 ,那么在进行第 次操作前, 的字典序一定要尽量小,这部分是固定的,而我们的状态 中就只需保存 这个长度 的串,时间复杂度降为 。
CF1442D
每个数组只能取其中的一个前缀,我们记从数组 取 的贡献为 ,由题可知 单增。
因此我们可以证明:不会同时存在两个取而又没有取完的数组。考虑反证,设这两个数组取了 ,不妨设 ,我们换成取 一定更优。反复调整,一定能使数组 取完。
因此,我们可以分别考虑那个没取完的数组和其它数组。其它数组可以直接缩成一个元素,枚举那个没取完的,对其它数组做一个背包,然后再和该数组合并即可得到答案。
暴力做的时间复杂度为 的,无法接受。考虑一个经典题:依次询问删去每条边后的全源最短路,做法是利用分治和 floyd。floyd 和背包本质上都是增量算法,因此我们可以用到这题。具体而言,对于当前区间 ,我们先把 的数组加入背包,然后向 递归;回溯时将背包还原,并把 的数组加入背包,向 递归。分治到 时,背包里就是除了 的所有数组了,此时做一个合并即可。
时间复杂度为 。
CF1400F
第一眼看上去不好做,因为这个“-prime 区间”并没有什么优秀的性质。但又发现 的上界很小,爆搜一下,发现最多有 种 -prime 区间。
然后问题转化为给定 个模式串,要求从长度为 的文本串中删除最少的字符使得没有一个模式串在文本串中出现过。
这是一个 AC 自动机典题。将模式串的 AC 自动机建出来,设 表示将 放在自动机上走到了节点 时 能保留的最长子序列(即不经过有结尾标记的点),转移即可:
时间复杂度为 。
CF1394D
单增链和单减链本质相同,我们不妨给每条边定个向,使得每条链按边的方向时单增的。
对于一条边 ,若 ,那么这条链的方向就是固定的了;否则我们可以给它随意定向。
假设我们已经完成了定向,对于一个点 ,它的入度为 ,出度为 。既然我们想让贡献最小化,那么我们就需要最小化经过该点的链的条数,一个入链可以和一个出链匹配,多余的边只能断在点 ,所以我们可得经过点 的链有 条。
我们想最小化 ,考虑用树形 dp 来解决。
设 表示当 是入边/出边时子树 的最小贡献。在计算点 的贡献时,实际上我们只关心 和 的数量,并不关心具体哪些边入、那些边出。我们设 为 的未确定方向的儿子,一开始的时候我们令其全为出边;当一条出边变为入边时,无论变的是哪条, 的变化都是固定的。因此,最优的策略时我们每次挑一个 最小的 改变。
时间复杂度为 。(注意需要特判根,因为根没有父亲)
CF1393E1
这个做法比较呆瓜。
在每个字符串的末尾添加一个值为 'a'-1
的字符,不影响大小比较(注意,该字符和空字符比较时要相等)的同时,不删字符就相当于删掉末尾这个字符。
考虑这是一个形如 lis 的东西,考虑 dp 解决。对于每个字符串,我们将其删去每一个字符得到的 个串按字典序排序。设 表示以第 个字符串的字典序第 小的生成串结尾的 lis 的个数,它可以对所有满足 的 产生贡献,注意到满足条件的 形成一个后缀,差分一下即可。
现在我们要实现的是比较两个串在各删去自己的某个字符的情况下的字典序大小。将这两个串 和 对齐,设靠前删去的那个字符在位置 ,靠后的在位置 ,我们需要比较 和 ,相同的情况下再比较 和 ,相同的情况下再比较 和 。若我们将所有串合并成一个大串,实际上我们只要实现任意两个区间的字典序大小比较。
这是一个比较经典的问题,若比较 和 ,先判断是否有前缀关系,然后就比较后缀 和后缀 ,用后缀数组即可完成,是否有前缀关系也能用后缀数组和 ST 表完成。于是就可以 大小比较。
排序复杂度为 ,后面 dp 复杂度为 。
EX
关于大小比较,有一些不同的实现:
- 经典二分+哈希,单次比较的复杂度为 。这样的总复杂度为
- 对于 和 ( 和 同理)删字符的比较,不妨设 删位置 , 删位置 ()。对于 和 与 和 的比较是固定的,可以预处理,而中间就相当于错开一位的比较: 与 ,我们预处理出 表示 后面第一个满足 的 ,然后就可以实现比较。单次比较的复杂度为 。
CF1389G
众所周知,边双可以经过恰当的定向成为强联通分量,一个边双可以全选择有向边,代价为 。
之后考虑将边双缩起来:如果原本该点双内有特殊点,缩点后该点就是特殊点;缩点后点的收益为对应边双收益之和。这样以来,原问题就转化到了树上。
考虑我们会选取怎样的边为无向边。仔细思考后发现无向边集构成一个联通块,因为若不是一个联通块(设其为联通块 和 ),那么一定存在 或 的单向到达关系,无论是所有特殊点都能到达 还是 ,把另一个联通块全改为有向边都不会对收益产生影响,而代价一定会减小,因此不是一个联通块一定不是最优的。
我们考虑将这个联通块拎起来作为根(除去这个联通块剩下的点构成森林),所有不在该联通块的特殊点到该联通块的路径一定一路向上,而森林中还存在一些树种没有特殊点,将这些树定为叶向树,那么所有特殊点也都能到达这些森林。
因此,我们将没有特殊点的子树一路向上合并,直到合并到了有特殊点的子树根。这样以后,净收益就是选择的联通块的点集收益之和减去边集代价之和。
这样以后我们可以先以 为根(假定联通块包含 ,即钦定 是饱和点),然后做一个简单的树形 dp:设 表示钦定联通块包含 的情况下子树 的最大净收益,转移方程则为
这个 dp 是容易换根的,这样就能得到钦定每个 是饱和点时的最大净收益 ,正好解决了整个问题。
CF1383E
solution 1
考虑这个操作具体能实现什么效果:
- 删去任意一个
- 删去任意一个 ,且不能删完一个 的连续段。
而最终得到的序列也一定是原序列的子序列,直接对操作计数不好避免算重,考虑对结果序列计数。
直接计数也是不好做的,需要记录当前末尾连续 的个数,状态数就达到了 ,考虑进行映射计数。
假设已知一个结果序列,考虑如何从原序列生成它。设已经生成了前 个数,第 个数是原序列的第 个数,现在准备生成第 个数,当前末尾有 个 。(不妨令当前结果序列中已经含有了 )
- 第 个数是 ,找到 中的第一个满足 的 ,将 这一段 删光,就能得到 :。
- 第 个数是 ,找到 中第一个满足 全是 的 ,将结果序列末尾的 个 删掉,把 中所有的 删掉, 中剩下的 与结果序列中已有的最后一个 合并在了一起,把它们删成只剩一个,就能得到对应的 :
- 若结果序列长度仅为 ,则要满足 小于等于原序列末尾 的个数。
因此我们可以用一个生成序列 表示一个结果序列 。比如从 10100
生成结果序列 100
对应的生成序列就为 1 2 5
。结果序列和生成序列构成双射,对结果序列计数也就等价于对生成序列计数。
设 表示原序列中以位置 结尾有 个 ,根据上面生成序列的构造方法,若 ,那么结果序列中以位置 结尾有 个 ;若下一个位置 ,那么我们就可以推断出 为 中第一个满足 全是 的 ,若倒着考虑,即能得到 从 转移而来。
因此我们可以倒着 dp,设 表示考虑了 ,存在 的生成序列的个数,顺便维护一个 表示 中第一个满足 的 ,转移为:
- 以 为末尾,要满足 ,则 。
- 生成序列中后一个数是 ,则 。
- 生成序列中后一个数是 ,则 。
最后特殊考虑一下原序列中开头的 即可。
时间复杂度为 。
solution 2
其实是可以对结果序列直接计数的。(T 宝做法)
首先还是将开头和末尾的 先去掉。我们设 表示由原序列 生成的结果序列个数(其中 ), 表示 中第一个满足 、 中 的个数为 、 的 ,转移为(设 中 的个数为 ):
- 当前 所在的 连续段是结果序列中末尾的的 连续段,则 。
- 当前 所在的 的连续段长度为 ,后面接着一个长度为 的 连续段,再后面是 连续段,则:
暴力转移是不行的,瓶颈在于维护 的和,考虑计算 对 的贡献:
设 都满足 、,令 ,则 乘上 中 的个数就是其对 的贡献。换句话说,我们记 表示 中第一个满足 、 的 ,每经过一个 的位置,令 ,那么 就是全体"后面接着一个长度为 的 连续段"形态的贡献之和。
实际上我们也不用区分 ,可以直接维护一个 , 发生改变时维护 ,每经过一个 的位置就令 。
时间复杂度为 。
CF1379E
solution
这个构造方法的正确性我不太会证,但它确实是对的。
首先偶数一定构造不出,先判掉。
考虑一下两个极端:最多能有几个不平衡点、最少能有几个不平衡点。
最多:考虑如下的一条链,存在 个不平衡点。
最少:构造一颗完全二叉树。若是满二叉树,则有 个;否则有 个。
特殊处理一下,并把 的判为无解,接下来尝试构造。
我们可以花 个点构造上述这样一个有 个不平衡点的链(要求链底要接上一个大小 的左儿子,在上图就是子树 要 ),令 ,构造这样一个链剩下了 个点,因为 ,所以 一定 ,是符合要求的。
于是我们尝试将这 个点构造成一颗完全二叉树接在下面,如果不是满二叉树,皆大欢喜,总共有 个不平衡点;而如果是满二叉树,就还是只有 个不平衡点,需要继续处理。
于是我们考虑从这颗满二叉树中薅掉两个点,使它不为满二叉树,这时链底有 个点,仍然符合 的要求,链上还是 个不平衡点,链底有一个不平衡点,总共 个。但是我们薅下的两个点还要接上去,最好的选择就是接在根节点的右儿子下面,这样只要根节点的左子树够大,根节点仍然是不平衡的;否则判定它无解。
这样就能过了,时间复杂度为 。
补充:上面这种构造方法根的左子树不够大的情况实际上只有一种 n=9,k=2
,爆搜后其实可以得到这种情况确实无解。
CF1327G
和出现次数有关的有两个东西:后缀自动机和 AC 自动机,前者是用文本串建立,后者是用模式串建立。本题中模式串固定,文本串未知,于是思考方向就偏向 AC 自动机。
将模式串的 AC 自动机建出来,在结尾节点加上价值。走到一个节点新产生的价值就是 fail 树上该节点到根的价值和,做一次前缀和预处理一下便可以求出。假设我们知道了文本串 , 在自动机上跑一遍的价值和就是 的价值。
是不确定的,但能拆成至多 段确定的子串。确定的子串从确定的点开始跑,最终到达确定的点,获得确定的价值。我们设 表示第 段子串从节点 开始最终跑到的节点, 表示这一过程中获得的价值,接下来就好做了。
因为要求不能重复,状压一下已经用过的字符。设 表示已经完成了前 个问号及子串,到达节点 的最大价值,转移时枚举枚举问号是那个字符即可。
时间复杂度为 。
CF1322D
相同的往大的合并,如果我们从小到大考虑就没有后效性,于是将序列翻转,单调不升变为单调不降。
设考虑到第 个人,钦定已选的人尽可能晋级到最大攻击力 ,且有 个攻击力为 的人()时的最大净收益为 ,转移为:
- 不选第 个人,。
- 选了第 个人,。
- 下面选的人攻击力要大于等于 ,对于 ,考虑从 到 的晋级,
转移 ,看上去总复杂度是 的(因为状态看上去是 的),但实际上对于每个 ,当 时第三维大小至多为 , 时第三维大小至多为 ,……,因此,对于每个 ,后两维有用的状态是 级别的。
于是总时间复杂度实际上是 。
CF54D
kmp+dp
设 表示填到第 位,匹配了模式串的前缀 是否可以(方案记录转移即可)。枚举 和当前为填哪个字符,失配的过程用 kmp 预处理后暴力跳即可,时间复杂度为 。
CF68D
开个 map 维护每个点的子树和 ,每次操作至多添加 个点,所以是能接受的。
修改操作暴力做,复杂度 。
查询操作递归进行,若子树和大于当前最大值就递归下去,否则子树内不可能产生贡献,返回。
左右子树递归的条件分别可以写成:,若同时成立即可得 ,所以不能同时成立,于是可得每次只会递归一边,复杂度也为 。
总时间复杂度为 。
CF51F
环缩完之后还是环,除非缩成了一个点,而所有边双都是由环构成的,于是我们在一开始就必须将所有边双缩掉,然后得到一棵树。
然后贪心,发现毛毛虫是一条链上挂一堆叶子,这条链和叶子是不用参与缩点的,于是要求链和叶子的点数尽量多,感性理解把原图所有叶子选上再选上直径即可。
最后如果不连通还要合并。
时间复杂度为 。
不懂为啥 tag 里有个 dp(或许求直径算是 dp)。
CF1428G1 & CF1428G2
首先可以发现可以直接忽略 和 ,因为 之间 的倍数都可以被表出。
还有一个发现是不能产生贡献的数位一定可以放在一个数里面,那么题目就可以转化为:有若干个物品,物品 为 ,数量为 个,选出若干个物品再选上一个整数使重量为 ,且价值和最大。
设 表示选出若干个重量和为 的物品的最大价值,这个可以用单调队列优化多重背包做。
然后考虑剩下的一个数,相当于有若干组物品,每组物品最多选一个,组 的物品分别是 ,价值同理;还有若干组物品 。要求选出物品价值和最大,这部分可以用分组背包来做。
总复杂度为 (忽略常数)。
deque 初始空间很大,开多了会 MLE,要手写双向链表模拟 deque。
CF1534F2
发现等价于使第 列从下向上的第 个格子掉落,记这些格子是 special 的。
对于每个 special 的格子预处理出能使它掉落的最左格子 和最右的格子 ,记 为 的掉落区间。
若两个 special 的格子的掉落区间有交(记相交部分为 ),那么选取 中最高的格子即可使两个 special 的格子都掉落。
推广一下,如果若干个掉落区间交集不为空,那么就可以扰动一个格子使它们都掉落。
问题转化为将 个区间 分成最少的组满足组内区间交集不为空。
似乎一眼无法做,但运用一次 Dilworth 定理(最小链划分等于最长反链),该问题等价于选取最多的不相交区间,前缀和优化 dp 即可。
时间复杂度为 。
CF453D
设 ,则转移可以写成 ,这部分可以用 fwt 做。
但问题是模数不是质数,不一定存在 的逆元,无法进行常规的 ifwt。
观察 fwt 和 ifwt 的转移矩阵:
我们可以将 ifwt 的矩阵提出标量 ,那么矩阵就和 fwt 一样了。
但是最后还是要除掉 啊……
同余的性质:
模数设为 最后再除掉即可。
时间复杂度为
CF704C
保证了每个变量最多出现两次,那么将变量和表达式之间连边,形成的图由若干条链和若干个环构成,那么我们记录第 个联通块异或值为 的方案数 ,最后就能通过一次 dp 求出答案。
为了方便起见,对连边做出以下调整:
- 对于 的情况,这个变量和表达式构成一个孤立的联通块,直接 。
- 对于 的情况,同理,直接 。
- 对于表达式只含一个变量 ,我们补成 ,计算时强制它选 即可。
然后图就很整齐了,链两端一定是两个变量,设 表示考虑前 个变量,第 个变量取 ,表达式异或和为 的方案数,转移即可。
对于环钦定开头选 然后 dp 两次即可。
时间复杂度为 。
CF582E
首先建出表达式树,考虑树形 dp。
看到这 很小,考虑状压每种取值下当前子树的取值,那么就可以设 表示 的子树,n 种取值为 的方案数。
考虑转移。叶子时枚举取哪种;不是叶子时,|
操作就是把状态位进行 卷积,&
操作就是把状态位进行 卷积,fmt 维护即可。
时间复杂度为 。
CF582D
什么牛马结论题。
有个叫库默尔定理的东西(我不是搞 MO 的我怎么知道啊):在 进制下( 为质数), 和 做加法的进位次数等于 基本算术分解中 的指数。
(证明考虑阶乘的基本算术分解即可。)
然后就是傻逼数位 dp 了,设 表示考虑了前 位,进位次数为 次,第 位是否向第 位进位,前 位是否小于等于 的前 位的方案数。
然后就是一大坨的转移,建议实现一个函数 用于 计算一个数位上两个数相加在 间的方案数方便转移。
时间复杂度为大常数 。
CF1392G
技不如人,甘拜下风。
发现进行 的操作就等价于对于 进行 的操作,再对 进行 的操作。(草这是怎么想到的)
然后就将区间转化为了后缀,进行了 操作的 序列可以 求出来(从后向前扫,维护 表示位置 进行了 操作后在哪,然后 swap(pos[x],pos[y])
即可。)
因为 中 的数量固定,考虑最大化 共同的 的数量即可。
考虑枚举共同部分 ,问题转化为求一个 满足 且 ,分别对 做 fmt 即可。
时间复杂度为 。
AGC012D
solution
取原序列的逆排列(规定重量为第一关键字、初始标号为第二关键字),则原本的交换操作可以看成“固定的几个位置之间可以交换值”。任意一种排列都可以通过若干次交换得到,那么,我们将能交换的两个位置连边,则一个联通块内我们可以任意排列。
根据多重集的组合数,我们设一个联通块的大小为 ,其中颜色 有 个,那么该联通块的排列方案则为 。
如果我们知道联通关系,那么就是好做的(每次统计一个联通块即可)。暴力连边的复杂度是 ,无法接受,考虑去掉不必要的连边。
同一种颜色间的交换,我们可以将最轻的那个点作为“中介”,即我们把该种颜色的每一个点向该种颜色中最轻的点连边(如果能的话),联通性不变。
不同颜色间的交换,我们考虑取最轻的两个不同颜色的球,记为 和 。对于颜色 的点,我们向 连边;对于其它颜色,我们向 连边。(如果能的话)
这样连边的数量是 级别,能够接受。
trick
的实现中并没有显式建图,考虑这题的特殊性质:
同色的点若能交换,则它们是等价的(和别的球交换时利用最小的作为中介),可以将它们的重量全部标为同色中最轻的那个。之后考虑最后其实只有一个大小 的联通块,其中出现了每种的一种前缀,那么就可以简单处理出每种颜色的前缀大小即可。这样做减小了常数。
AGC015D
solution
,即我们只能通过或操作得到更大的数,那么我们就可以考虑如何得到 的数。
我们设 的最大 的幂为 ,位运算不可能凭空产生位,所以我们可能得到的最大的数即为 ,因此我们只需考虑 中的数即可。首先我们假设 (即我们能取到 ),那么对于 中的数,我们可以或上 来得到 ;假设 的最高位为 ,那么我们可以用 中的数与 或得到 。然后我们发现也就只能得到这些数了。
因此此时的答案就是 ,简单统计即可。
但是可能 并不在 中,这种情况直接分析不好做,考虑将其转化为 的情况。
若 的二进制表示下有一段前缀相同,那么 中所有数都有这段前缀,操作后得到的数也一定有这段前缀,于是我们可以将这段前缀去掉,对答案无影响。去掉后, 就一定在 中了。
时间复杂度为 ,注意 和去前缀后 的特殊处理。
trick
实现没有什么技巧,本身就很好写。
AGC021C
solution
有个 naive 的想法。对于 为奇数,我们先将横着的骨牌在最后一行放满;对于 为奇数,先将竖着的骨牌在最后以列放满。之后只剩下一个长宽都是偶数的方格,我们使用:
两种 的方块将其填满即可(若剩下的骨牌个数是奇数,则最后一个 或 的骨牌单独占一个 的方格)。
交上去 个测试点只错了 个,基本可以说明这种思路没有大问题,只是还有 corner case 没有考虑到。
考虑一种情况():
我们发现其实是能放下的,但是按照我们上面的摆法是不能的。
问题出在哪?我们发现若 和 的骨牌都是奇数个,最后一块各占了一个 大方格,这样是浪费的。
但是这种情况并不是很多,条件很苛刻,需要满足 都是奇数,且将第 行和第 列填满后两种骨牌都剩奇数个,而改变的其实只有右下角的 区域,即从这种:
变成了这种:
特判一下即可。
时间复杂度为 。
trick
实现没有什么技巧,似乎所有人写的都比较烦琐(毕竟是分类讨论题),注意可以先将特殊情况看成普通情况,最后修改右下 的区域即可。
AGC020E
solution
首先我们考虑不是求“给定串所有子集的方案数”,而是求“给定串的方案数”怎么做。
这是一个很经典的区间 dp。为了防止算重,我们记 (S)
、0
、1
这种串为单位串,设 表示区间 的方案数, 表示区间 变为单位串的方案数。 的转移我们枚举第一个单位串, 转移我们枚举循环节,即:
现在考虑转成子集,设 表示串 的方案数, 表示串 变为单位串的方案数,转移是类似的。
( 的转移就是将每个循环节并起来)
这样做看上去好像不太行,但是加上记忆化后实际跑起来是很快的。
时间复杂度 。
trick
榜上前 3 的实现大同小异,基本都是将 以一个函数和一个 map
来实现,其中毛爷爷()的实现没有写 函数,而是在 函数中统一处理(常数或许会小一点?)。
AGC040C
solution
直接考虑似乎困难,考虑换一种角度看 AB
。我们将序列黑白染色,并令偶数位上的 AB 反转,那么原序列中的 AB
在新序列中就是 AA
或 BB
,我们发现即使经过若干次操作这还是成立的,因为我们每次消除一奇一偶两个位置。
限制转化为我们不能消除相邻的两个 A 或 B,这是一个经典问题,考虑每次会用一个非 A 的数去消掉一个其相邻的一个 A,并且这样的数在 A 或 非 A 数其中一种被消完之前一定存在(B 同理),那么能消完序列的充要条件是 A 和 B 出现的次数都不能超过序列长度的一半。
如果直接去计算不好处理,考虑用 减去 A 或 B 出现次数超过序列长度一半的方案数,这个是好算的,因为只会有一种数出现次数超过一半,枚举这种数出现次数 ,那么答案即为:
trick
实现没有什么技巧,本身就很好写。
AGC055C
solution
考虑寻求存在满足条件的排列 的充要的条件。
首先显然的是假设整个序列的 LIS 长度为 ,那么 ,因为去掉一个 LIS 里的数之后剩下的 个数仍然构成 LIS,长度至少为 ,并且还可能去掉后无影响即长度为 。
下面定义必经点、非必经点、无用点。
- 若位置 在所有 LIS 中,即去掉位置 后 LIS 长度减少,那么称 为必经点,有 。
- 若位置 在某个 LIS 中,且去掉位置 后 LIS 长度不变,那么称 为非必经点,有 。
- 若位置 不在任何一个 LIS 中,那么称 为无用点,有 。
首先我们考虑整个 序列都是一种值的情况。
- 全都是 ,即全是必经点,显然满足条件的只有 时 。
- 全都是 ,考虑非必经点一定是成对出现,当一对点中的一个点被删掉,另一个点能发挥等效的作用,即一对点对 LIS 产生长度为 的贡献,那么可得 个非必经点至多产生 的贡献,必要条件就是 ,这个序列由 个非必经点和 个无用点构成。这个条件也是充分的,考虑形如
接下来就只考虑 存在两种值的情况,分析 的取值范围。假设有 个必经点,那么会产生 的贡献,可得 ;非必经点也可能对 产生贡献,假设将必经点的位置作为切割点将整个序列分成若干段,我们发现只有同段的点才可能等效,那么可得每对非必经点都在一段内,设每段的长度为 ,那么至多有 对非必经点,可得 。
分析出了必要条件是 。似乎分析不出更多了,那么就大胆猜测这就是充要条件(的确是充要的,如果是打比赛就可以直接开始数数了)。我们尝试通过构造方案来证明这是充分的。
设 个必经点分别为 ,必经点和非必经点合并在一起构成 。
我们首先考虑填上无用点,考虑对于前 个无用点我们依次填上 ,对于后 个无用点依次填上。这样做是不合法的当且仅当 填到了 的后面或 填到了 的前面,想要满足这样必须要满足 并且不存在非必经点,这种情况下 ,但是题目要求 ,所以非法的情况不存在,我们这样填一定是可以的。
之后考虑非必经点被必经点划分成了若干段( 对应的值域为 ),处理方法和上面“全都是 ”一样,假设给第 段(设值域为 )分配了 个贡献,那么填上 即可。
于是我们证明了充分性。
之后考虑如何数这个东西,枚举必经点数量 和非必经点对数量 ,使用插板法然后乘上 的方案即可:
trick
实现没有什么技巧,本身就很好写。
AGC013C
solution
考虑两只蚂蚁碰撞折返的这个事件,假设我们只关系最后蚂蚁的分布,而不关心具体是哪一只,那么我们就其实可以忽略“折返”这一动作,相当于两只蚂蚁互换身份后继续向前爬。
于是我们就能计算出最终蚂蚁的坐标,假设初始有一只位置在 ,方向为顺时针的蚂蚁,那么最后将会有一只位置在 的蚂蚁(逆时针同理)。
现在我们还需要得出每个位置具体是那只蚂蚁。我们发现因为两只蚂蚁碰撞后就会折返,那么实际上最后蚂蚁们的相对位置相较于开始是不变的,即,假设这是在序列上动,那么将初始坐标和终止坐标排序,初始坐标第 大的蚂蚁最后坐标也是第 大。
但是考虑到现在蚂蚁在环上运动,“大小”在循环意义下失去了原本的作用。考虑一件事,假设有一只蚂蚁从坐标 爬到了坐标 ,那么蚂蚁的顺序其实就循环左移了一位;假设有一只蚂蚁从坐标 爬到了坐标 ,那么蚂蚁的顺序其实就循环右移了一位。并且,我们在这时候不关心这些影响具体是那些蚂蚁产生的(只关系最终顺序移动了几位),那么我们仍可以用忽略折返的思想去计算:假设初始有一只位置在 ,方向为顺时针的蚂蚁,那么它会使顺序循环右移 位(逆时针同理)。
时间复杂度为 (因为需要一次排序)。
implementation
榜上前 4 均采用此做法,但实现的方式有优劣。(提交记录)
AGC018C
solution
直接构造方案似乎较为困难,考虑我们给每个人随便钦定一种币,满足 个金币、 个银币、 个铜币的数量限制,然后调整方案使其更优。
考虑调整后每种币的数量不变,那么其实一次调整可以看成一个长度为 的置换(记 为金币, 为银币, 为铜币),分别为: ,其中一个 的交换即为将一个原本为 币的人的 币贡献减掉,加上他 币的贡献。我们可以开 个大根堆记录 种 交换每种最优的那个,并每次选出 种贡献和为正数的交换拼成一个置换,改变分配方式并加上贡献;当最优的 种贡献之和非正的时候,说明我们再操作就不优了,那么就结束操作。(可以用反证法证明如果存在一个置换的贡献为正数,那么选取最优的 个能拼成置换的交换的贡献之和一定为正数)
时间复杂度为 。
这个做法是我的做法, (提交记录)赛时写的也是这个做法。这个做法码量可能较大(主要来自 种置换的分类讨论,但是写的时候每一类长得很像,Ctrl C+Ctrl V 即可解决大半,所以实际写起来并不慢)
另外还有一种做法,长度较短,榜上 rk3 (提交记录)写的是这种做法:
换一种角度,我们初始钦定全选金币,并将三元组 变为二元组 (分别代表改选银币和铜币的贡献),问题变为选 个银币和 和铜币使的贡献最大。
我们考虑一个二元组 选了 ,另一个二元组 选择了 ,那么这种选法比交换 的选法优当且仅当 即 。那么我们按照 按降序排序,一定存在一个位置 使得选的银币都在前缀 中,铜币都在后缀 中。
这样我们可以用堆预处理出每个前缀选 个银币的最优贡献和每个后缀选 个铜币的最优贡献,最后枚举断点 即可。
时间复杂度也为 ,但常数会小很多,也会更好写。
implementation
两种实现的内部并无太多 trick 可言。
AGC027B
solution
首先捡起垃圾消耗 ,那么最后一定会消耗固定的 ,这部分可以最后加上。
一开始写了个假做法,认为每次捡的垃圾一定是连续一段,捡一段区间的垃圾的代价是好算的(下面会提到怎么算),于是就设了一个可以斜率优化的 dp,交上去挂了一半点,然后对拍发现了 dp 是假的。
其实考虑一种形态:靠近 的地方有一大堆点 ,靠近 的地方又有一大堆点 ,此时最优的策略从后面拿一个点 ,再从前面带一个点 ,如此反复,这样做就不是连续一段了。
但考虑我们原来代价计算的方式还是没有问题的,即假设我们捡了一个点集 (其中 ),我们最优的捡的方法一定是先空手走到 ,然后边往回走边捡垃圾,考虑这样做的代价:
- 第一步,空手走到 ,消耗 。
- 第二步,依次捡起垃圾,代价形如:。根据我们小学奥数的知识,平方可以拆成奇数等差序列的求和,即:,那么贡献就可以写成“ 对答案产生了 的贡献的形式”(去除 )。
- 扔掉垃圾,消耗 。
因此处理点集 的代价即为:。
我们发现每次扔垃圾会消耗固定的额外代价 ,接下来考虑枚举扔垃圾的次数 ,此时额外代价为 ,那么计算运动的最小代价即可。
考虑 序列是固定的, 序列也是固定的,相当于我们要给两个定序列安排顺序,使得同位置乘积之和最小。这是一个经典贪心问题,结论是应小的乘大的,大的乘小的(一个升序,一个降序)。那么我们就可以先选最大的 个 作为 个序列的 ,第二大的 个 作为 个序列的 ……,以此类推。我们发现不同序列的相同位置前乘系数是一样的,于是我们就可以枚举位置,用 的前缀和进行快速计算,时间复杂度为 。
于是总的复杂度即为调和级数 。
榜上前 4 均使用此做法。
implementation
实现本身就很好写,没有什么技巧,但是需要注意虽然最后答案不会爆 long long
,但对于某些 的答案可能会爆,中途应采用 ``__int128` 计算((提交记录)),或超过 时即将当前的 舍弃((提交记录))。
AGC009C
solution
第一眼的想法是 2-SAT 求方案数,但是这是很困难的问题,考虑换一种思路。
对 升序排序后,我们发现只有当前 集合中最后一个数和 集合中最后一个数会对当前数的决策产生影响,那么就很容易设出一个 dp 状态 表示考虑了前 个数, 集合的最后一个数是第 个数, 集合的最后一个数是第 个数的方案数。
但是这样肯定是无法接受的,状态数就已经是 了。考虑这个状态设计中的浪费: 和 中一个数一定是 。那么就可以舍弃一维,设 表示考虑了前 个数,第 个数放在 集合还是 集合,另一个集合中最后一个数是第 个数的方案数,这样状态数就变为了 级别,预处理后转移可以 进行,总复杂度是 。
似乎很难再优化状态了,但是考虑一下我们的转移是简单的(设 表示最大满足 的 , 表示最大满足 的 ):
发现这些操作可以用两颗支持区间求和、单点加、全局赋 的线段树即可维护,时间复杂度降为 。(提交记录)
implementation
看了榜上前 3,发现我的做法是小丑。
的做法:不妨令 并判掉无解,设 表示 集合最后选的是第 个数,考虑上 集合上一个选的是第 个数,那么 需要满足 (记为条件 )且对于 中任意的 要满足 (记为条件 )。那么实际上满足条件 的 构成了一段区间,满足条件 的 也构成了一段区间,于是转移范围就是一段区间 ,且区间左端点 随 单调不减,双指针和前缀和维护一下即可,时间复杂度 。(提交记录)
另外还有一些和 做法大同小异的做法(比如用二分和树状数组来实现双指针和前缀和),本质相同。
AGC037E
solution
我们设整个字符串里最小的字符为 ,那么我们首先要最大化字符串开头连续 的个数,否则一定不优。
发现我们可以通过一次操作将整个字符串字符串中最长的一段连续 放在字符串末尾,之后每次操作都能使末尾的这段 倍长,这样我们就使得了开头的 尽量长。
之后的目标就是使得去掉这段 后的字符串字典序最小,那么我们来考虑这段字符串是如何形成的。
记字符串 的翻转为 , 表示 个字符 拼起来得到的字符串。设我们第一次操作(截取最长 ,如果初始字符串末尾的连续 已经是最长的话就取倍长过一遍的)后的字符串为 ,一次倍长后变为 ,如此反复, 次操作后得到 ,截取 。最后一次操作倍长并截取后半段得到 。于是我们就需要考虑取到最小的 。因为 在 中出现过,所以 也一定在 中出现过,并且 是字典序最小的,于是我们取 中最小的长度为 的子串,并将开头的连续 倍长 遍即可( 一开始与 取一下 )。
这样无需任何 corner case 的特判。
implementation
直接 暴力即可。注意到 string
自带比较字典序,可以用 string::substr()
截取子串并取 ,写出来更清爽。(提交记录)
因为代码很好写,榜上前几的实现也没有什么特殊的。
AGC050D
solution
考虑经过 轮且已有 个人获胜,那么剩下所有人手中的牌数和没被别人选过的牌数都是固定的,分别是 和 。实际上一个局面可以由当前是第几轮、还有几个没获胜的人、当前轮到没获胜的人中的第几个人操作三个参数确定,再加上我们想知道对于每一个 ,第 个人的获胜概率,加上这个 ,一共四个参数即可确定状态。
设 表示当前已经完成了 轮(第 轮正在进行),轮到了剩下的人中的第 个人,我们期望知道获胜概率的人前面剩 个人,后面剩 个人(一共剩 个人)的情况下,后面(包括当前)进行若干次操作后这个期望的人的获胜概率。
分情况讨论转移即可(设当前人获胜概率为 ,失败概率为 ):
- 当前的人就是期望的人。 的概率直接获胜,否则 的概率在后面获胜。
- 当前的人在期望的人前面。 的概率当前人胜,然后期望的人 概率在后面获胜; 概率当前的人败,然后期望的人 的概率在后面获胜。
- 当前的人在期望的人后面。 的概率当前人胜,然后期望的人 概率在后面获胜; 概率当前的人败,然后期望的人 的概率在后面获胜。
时间复杂度为 。(假设 同阶,下文同理)
implementation
使用记忆化搜索实现则会很好写。(提交记录)
(提交记录)、 (提交记录)等人包括题解都使用了一种 的 dp(状态有一维多余,转移采用了比较劣的写法),时间复杂度不如上面的优,也不如上面的这种好写。
最优解 (提交记录)采用的也是上面的 dp 方法,但是使用了普通的迭代实现,代码难度增加,但是常数有所减小。(从 40ms 变为 11ms)
CF1455F
solution 1
前 次操作只会影响到 ,并且在第 次操作前,原本在位置 的数只可能在 或 。
于是就可以考虑设 表示完成了前 次操作,原本在位置 的数现在在位置 时 的最小字典序,每种操作模拟一下即可。
状态数为 ,每个状态为一个 的string
,于是时间复杂度为 ,能过。(优点:好写)
solution 2
进一步分析,第 次操作只会影响到 ,那么在进行第 次操作前, 的字典序一定要尽量小,这部分是固定的,而我们的状态 中就只需保存 这个长度 的串,时间复杂度降为 。
CF1442D
solution
每个数组只能取其中的一个前缀,我们记从数组 取 的贡献为 ,由题可知 单增。
因此我们可以证明:不会同时存在两个取而又没有取完的数组。考虑反证,设这两个数组取了 ,不妨设 ,我们换成取 一定更优。反复调整,一定能使数组 取完。
因此,我们可以分别考虑那个没取完的数组和其它数组。其它数组可以直接缩成一个元素,枚举那个没取完的,对其它数组做一个背包,然后再和该数组合并即可得到答案。
暴力做的时间复杂度为 的,无法接受。考虑一个经典题:依次询问删去每条边后的全源最短路,做法是利用分治和 floyd。floyd 和背包本质上都是增量算法,因此我们可以用到这题。具体而言,对于当前区间 ,我们先把 的数组加入背包,然后向 递归;回溯时将背包还原,并把 的数组加入背包,向 递归。分治到 时,背包里就是除了 的所有数组了,此时做一个合并即可。
时间复杂度为 。
本题做法单一。
CF1400F
solution
第一眼看上去不好做,因为这个“-prime 区间”并没有什么优秀的性质。但又发现 的上界很小,爆搜一下,发现最多有 种 -prime 区间。
然后问题转化为给定 个模式串,要求从长度为 的文本串中删除最少的字符使得没有一个模式串在文本串中出现过。
这是一个 AC 自动机典题。将模式串的 AC 自动机建出来,设 表示将 放在自动机上走到了节点 时 能保留的最长子序列(即不经过有结尾标记的点),转移即可:
时间复杂度为 。
本题做法单一。
CF1394D
solution
单增链和单减链本质相同,我们不妨给每条边定个向,使得每条链按边的方向时单增的。
对于一条边 ,若 ,那么这条链的方向就是固定的了;否则我们可以给它随意定向。
假设我们已经完成了定向,对于一个点 ,它的入度为 ,出度为 。既然我们想让贡献最小化,那么我们就需要最小化经过该点的链的条数,一个入链可以和一个出链匹配,多余的边只能断在点 ,所以我们可得经过点 的链有 条。
我们想最小化 ,考虑用树形 dp 来解决。
设 表示当 是入边/出边时子树 的最小贡献。在计算点 的贡献时,实际上我们只关心 和 的数量,并不关心具体哪些边入、那些边出。我们设 为 的未确定方向的儿子,一开始的时候我们令其全为出边;当一条出边变为入边时,无论变的是哪条, 的变化都是固定的。因此,最优的策略时我们每次挑一个 最小的 改变。
时间复杂度为 。(注意需要特判根,因为根没有父亲)
nick 的提交(这份实现中没有特判根,而是增加一个状态 表示忽略 )
CF1393E1
solution
我的这个做法比较呆瓜。
在每个字符串的末尾添加一个值为 'a'-1
的字符,不影响大小比较(注意,该字符和空字符比较时要相等)的同时,不删字符就相当于删掉末尾这个字符。
考虑这是一个形如 lis 的东西,考虑 dp 解决。对于每个字符串,我们将其删去每一个字符得到的 个串按字典序排序。设 表示以第 个字符串的字典序第 小的生成串结尾的 lis 的个数,它可以对所有满足 的 产生贡献,注意到满足条件的 形成一个后缀,差分一下即可。
现在我们要实现的是比较两个串在各删去自己的某个字符的情况下的字典序大小。将这两个串 和 对齐,设靠前删去的那个字符在位置 ,靠后的在位置 ,我们需要比较 和 ,相同的情况下再比较 和 ,相同的情况下再比较 和 。若我们将所有串合并成一个大串,实际上我们只要实现任意两个区间的字典序大小比较。
这是一个比较经典的问题,若比较 和 ,先判断是否有前缀关系,然后就比较后缀 和后缀 ,用后缀数组即可完成,是否有前缀关系也能用后缀数组和 ST 表完成。于是就可以 大小比较。
排序复杂度为 ,后面 dp 复杂度为 。
我的提交(判断是否有前缀关系用的是字符串哈希,常数比 ST 表小)
trick
关于大小比较,有一些不同的实现:
- 经典二分+哈希,单次比较的复杂度为 。这样的总复杂度为 (题解做法)
- 对于 和 ( 和 同理)删字符的比较,不妨设 删位置 , 删位置 ()。对于 和 与 和 的比较是固定的,可以预处理,而中间就相当于错开一位的比较: 与 ,我们预处理出 表示 后面第一个满足 的 ,然后就可以实现比较。单次比较的复杂度为 。jiangly 的提交、Um_nik 的提交
CF1389G
solution
众所周知,边双可以经过恰当的定向成为强联通分量,一个边双可以全选择有向边,代价为 。
之后考虑将边双缩起来:如果原本该点双内有特殊点,缩点后该点就是特殊点;缩点后点的收益为对应边双收益之和。这样以来,原问题就转化到了树上。
考虑我们会选取怎样的边为无向边。仔细思考后发现无向边集构成一个联通块,因为若不是一个联通块(设其为联通块 和 ),那么一定存在 或 的单向到达关系,无论是所有特殊点都能到达 还是 ,把另一个联通块全改为有向边都不会对收益产生影响,而代价一定会减小,因此不是一个联通块一定不是最优的。
我们考虑将这个联通块拎起来作为根(除去这个联通块剩下的点构成森林),所有不在该联通块的特殊点到该联通块的路径一定一路向上,而森林中还存在一些树种没有特殊点,将这些树定为叶向树,那么所有特殊点也都能到达这些森林。
因此,我们将没有特殊点的子树一路向上合并,直到合并到了有特殊点的子树根。这样以后,净收益就是选择的联通块的点集收益之和减去边集代价之和。
这样以后我们可以先以 为根(假定联通块包含 ,即钦定 是饱和点),然后做一个简单的树形 dp:设 表示钦定联通块包含 的情况下子树 的最大净收益,转移方程则为
这个 dp 是容易换根的,这样就能得到钦定每个 是饱和点时的最大净收益 ,正好解决了整个问题。
ecnerwala 的提交(隐式缩点:标记树边、割边,将非割边和不含特殊的的子树内的边的边权清零,树形 dp 时沿着树边走)
CF1383E
solution 1
考虑这个操作具体能实现什么效果:
- 删去任意一个
- 删去任意一个 ,且不能删完一个 的连续段。
而最终得到的序列也一定是原序列的子序列,直接对操作计数不好避免算重,考虑对结果序列计数。
直接计数也是不好做的,需要记录当前末尾连续 的个数,状态数就达到了 ,考虑进行映射计数。
假设已知一个结果序列,考虑如何从原序列生成它。设已经生成了前 个数,第 个数是原序列的第 个数,现在准备生成第 个数,当前末尾有 个 。(不妨令当前结果序列中已经含有了 )
- 第 个数是 ,找到 中的第一个满足 的 ,将 这一段 删光,就能得到 :。
- 第 个数是 ,找到 中第一个满足 全是 的 ,将结果序列末尾的 个 删掉,把 中所有的 删掉, 中剩下的 与结果序列中已有的最后一个 合并在了一起,把它们删成只剩一个,就能得到对应的 :
- 若结果序列长度仅为 ,则要满足 小于等于原序列末尾 的个数。
因此我们可以用一个生成序列 表示一个结果序列 。比如从 10100
生成结果序列 100
对应的生成序列就为 1 2 5
。结果序列和生成序列构成双射,对结果序列计数也就等价于对生成序列计数。
设 表示原序列中以位置 结尾有 个 ,根据上面生成序列的构造方法,若 ,那么结果序列中以位置 结尾有 个 ;若下一个位置 ,那么我们就可以推断出 为 中第一个满足 全是 的 ,若倒着考虑,即能得到 从 转移而来。
因此我们可以倒着 dp,设 表示考虑了 ,存在 的生成序列的个数,顺便维护一个 表示 中第一个满足 的 ,转移为:
- 以 为末尾,要满足 ,则 。
- 生成序列中后一个数是 ,则 。
- 生成序列中后一个数是 ,则 。
最后特殊考虑一下原序列中开头的 即可。
时间复杂度为 。
solution 2
其实是可以对结果序列直接计数的。(T 宝做法)
首先还是将开头和末尾的 先去掉。我们设 表示由原序列 生成的结果序列个数(其中 ), 表示 中第一个满足 、 中 的个数为 、 的 ,转移为(设 中 的个数为 ):
- 当前 所在的 连续段是结果序列中末尾的的 连续段,则 。
- 当前 所在的 的连续段长度为 ,后面接着一个长度为 的 连续段,再后面是 连续段,则:
暴力转移是不行的,瓶颈在于维护 的和,考虑计算 对 的贡献:
设 都满足 、,令 ,则 乘上 中 的个数就是其对 的贡献。换句话说,我们记 表示 中第一个满足 、 的 ,每经过一个 的位置,令 ,那么 就是全体"后面接着一个长度为 的 连续段"形态的贡献之和。
实际上我们也不用区分 ,可以直接维护一个 , 发生改变时维护 ,每经过一个 的位置就令 。
时间复杂度为 。
CF1379E
solution
这个构造方法的正确性我不太会证,但它确实是对的。
首先偶数一定构造不出,先判掉。
考虑一下两个极端:最多能有几个不平衡点、最少能有几个不平衡点。
最多:考虑如下的一条链,存在 个不平衡点。
最少:构造一颗完全二叉树。若是满二叉树,则有 个;否则有 个。
特殊处理一下,并把 的判为无解,接下来尝试构造。
我们可以花 个点构造上述这样一个有 个不平衡点的链(要求链底要接上一个大小 的左儿子,在上图就是子树 要 ),令 ,构造这样一个链剩下了 个点,因为 ,所以 一定 ,是符合要求的。
于是我们尝试将这 个点构造成一颗完全二叉树接在下面,如果不是满二叉树,皆大欢喜,总共有 个不平衡点;而如果是满二叉树,就还是只有 个不平衡点,需要继续处理。
于是我们考虑从这颗满二叉树中薅掉两个点,使它不为满二叉树,这时链底有 个点,仍然符合 的要求,链上还是 个不平衡点,链底有一个不平衡点,总共 个。但是我们薅下的两个点还要接上去,最好的选择就是接在根节点的右儿子下面,这样只要根节点的左子树够大,根节点仍然是不平衡的;否则判定它无解。
这样就能过了,时间复杂度为 。
补充:上面这种构造方法根的左子树不够大的情况实际上只有一种 n=9,k=2
,爆搜后其实可以得到这种情况确实无解。
antontrygubO_o 的提交(递归处理,分出一个满二叉树左儿子,递归处理右儿子,与上面做法本质相同)
icecuber 的提交(同 anton 做法)
CF1327G
solution
和出现次数有关的有两个东西:后缀自动机和 AC 自动机,前者是用文本串建立,后者是用模式串建立。本题中模式串固定,文本串未知,于是思考方向就偏向 AC 自动机。
将模式串的 AC 自动机建出来,在结尾节点加上价值。走到一个节点新产生的价值就是 fail 树上该节点到根的价值和,做一次前缀和预处理一下便可以求出。假设我们知道了文本串 , 在自动机上跑一遍的价值和就是 的价值。
是不确定的,但能拆成至多 段确定的子串。确定的子串从确定的点开始跑,最终到达确定的点,获得确定的价值。我们设 表示第 段子串从节点 开始最终跑到的节点, 表示这一过程中获得的价值,接下来就好做了。
因为要求不能重复,状压一下已经用过的字符。设 表示已经完成了前 个问号及子串,到达节点 的最大价值,转移时枚举枚举问号是那个字符即可。
时间复杂度为 。
本题做法单一。
CF1322D
solution
相同的往大的合并,如果我们从小到大考虑就没有后效性,于是将序列翻转,单调不升变为单调不降。
设考虑到第 个人,钦定已选的人尽可能晋级到最大攻击力 ,且有 个攻击力为 的人()时的最大净收益为 ,转移为:
- 不选第 个人,。
- 选了第 个人,。
- 下面选的人攻击力要大于等于 ,对于 ,考虑从 到 的晋级,
转移 ,看上去总复杂度是 的(因为状态看上去是 的),但实际上对于每个 ,当 时第三维大小至多为 , 时第三维大小至多为 ,……,因此,对于每个 ,后两维有用的状态是 级别的。
于是总时间复杂度实际上是 。
本题做法单一。
__EOF__

本文链接:https://www.cnblogs.com/tiatto/p/16756649.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧