CF 2000± 选做
约定:
- 用集合符号表示位运算符号,用 \(\oplus\) 表示异或,特别的,\(i\in S\) 表示二进制数 \(S\) 的第 \(i\) 位为 \(1\)
- 用 \(V\) 表示值域,\(\Sigma\) 表示字符集,\(\omega\) 表示
bitset
的常数(\(\omega=64\))- 除去用 \(()/[]\) 表示开闭区间外,\([]\) 仅表示艾弗森约定,\(\{\}\) 仅表示集合,括号嵌套全用 \(()\)
- 字符串或序列角标为区间表示对应区间的子串
Even-Odd XOR
1500 tag: 构造
XOR
循环节
首先奇偶分类,则两两分组,想到可以让所有位置两两奇偶分组,然后每组满足要求就构造成功,但这里每个单独的组不可能满足要求,所以转战四个一组,这个就很简单了,利用 \(4k\oplus4k+2=4k+1\oplus4k+3\),那么现在问题就变成解决模 \(4\) 余 \(1,2,3\) 情况下的构造。
\(1\) 可以构造多一个 \(0\),\(3\) 可以构造多 \(1,3,2\),\(2\) 稍微麻烦些,因为单独两个无法构造,可以多一组构造 \(6\) 个的情况,直接构造 \(1,5,10,6,11,3\),再配合周期性构造就得到答案。
L-shapes
1700 tag: 暴力
稍微用 pair
来使代码变得更简洁。
Maximum AND
1800 tag: 贪心
XOR
不难观察得到是按位贪心,一个首先的想法是去维护一些集合,表示每个集合内部的 \(a,b\) 之间异或满足之前的答案,做第 \(i\) 位时,判断每个集合内是否能组合出第 \(i\) 位全都异或得到 \(1\),实现起来很麻烦,但是有更加简单的办法。
如果从高到低贪心的过程中判断两两异或后的结果的为 \(ans\),则一定满足:
满足这个条件,对于每个 \(x\),只要两个集合相等,一一组合构造就有答案,否则一定没有答案,用这个性质验证就好了,时间复杂度 \(\mathO{nlog|V|}\)。
Xor-Subsequence (hard version)
2400 tag: dp
XOR
0/1字典树
dp 转移方程形如:
对应的 easy version 是 1800 的水题,因为 \(a\) 值域只有 \(200\),异或时最多影响到 \(2^8=256\),所以暴力转移最多只要做 \(i-256\) 就好了。
本题的值域达到了 \(10^9\),需要更深地挖掘性质,首先思考如果将 \(\oplus\) 换成 \(+\) 怎么做,移项得到条件 \(a_i-i>a_j-j\),此时每一项只和一个下标有关,问题就得到了很好地解决。
异或没有这个优美的性质,移项的性质只对等式生效,从二进制数的比较入手,按位从高到低比较,那么一定是若干二进制位相等,接下来一个二进制位 \(a_i\oplus j>a_j\oplus i\)。
因为是按位从高到低,不难想到放到 \(0/1\) 字典树上做,因为每个项只和 \(i\) 或 \(j\) 相关,字典树中的键值是 \(a_i\oplus i\),此时就是如何判断两个数不相同那一位孰大孰小。
首先 \(a_i\oplus i\neq a_j\oplus j\),即 \(a_i\oplus i\oplus a_j\oplus j=1\),既然在这一位上要求 \(a_i\oplus j>a_j\oplus i\),说明 \(a_j\oplus i=0\),那么只要在每个字典树的节点上记录\(a_j\oplus j\) 走向这个节点时 \(a_j\) 这一位对应的 \(f_j\) 用于转移就好了。
Fibonacci Strings
2000 tag: 构造
堆贪心
题目要求转化成将每个数分裂成数个不相邻的斐波那契数的和,且所有数的分解能完美覆盖一个斐波那契数列的前缀。
一个粗劣的想法是贪心,因为一个斐波那契数可以分解为接下来两个斐波那契数的和,但是一个数的分解不能相邻,故用 \(Fib_i\) 表示从 \(0\) 开始的斐波那契数列 \(\{1,1,2,3,5,8,\cdots\}\) 的第 \(i\) 个,最后一定会分解成 \(Fib_{2k+1}=\sum_{i=0}^kFib_{2k}\),这启示我们从大到小考虑,因为我们可以将一个大数分解成若干小数。
先有一种粗劣的贪心,首先如果数列和不是斐波那契前缀和显然无解,否则从大到小考虑,将现有的所有数放到一个堆中,每次取最大的与上次取出的数不同的数,若比当前的 \(Fib_i\) 小,则无解,否则分裂出一个 \(Fib_i\),剩余部分扔回堆里。
这个贪心的是对的,所以需要证明。
首先证明若当前做到 \(Fib_i\),存在一个数比 \(Fib_{i+1}\) 还大,一定无解,因为就算把 \(Fib_{i+1}\) 按上述方法分解,因为比 \(Fib_{i+1}\) 大,还多出一个部分需要被分解成若干个斐波那契数,则一定有相邻或重合,无解。
那么对于当前的 \(Fib_i\),若有多个数比 \(Fib_i\) 大,本次做完后就会产生有一个数比 \(Fib_{i+1}\) 大,根据上文无解,而如果只有一个数比 \(Fib_i\) 大,一定要选,最后一种情况是有多个数与 \(Fib_i\) 相等,那肯定要从这些数里选,因为都相等,所以等价。
这样就证明完了,代码很简单,可以将证明中讲到的无解情况提前判掉也无所谓。
Burenka and Traditions (hard version)
1900 tag: dp
XOR
0/1字典树
观察修改的形式和代价的形式,首先,代价是区间长度一半上取整,而修改是区间同时异或,那么首先可以知道操作的区间不可能重合,因为对于任意重合操作,一定可以将重合部分取出来单独操作,一定不会更劣,因为是一半上取整,也可以发现所有长度的操作一定可以转化成长度为 \(1\) 或 \(2\) 的操作,且代价都为 \(1\)。
那么就发现整个序列最劣用 \(n\) 次长度为 \(1\) 的操作实现,较优的情况时能分割出尽量多的小区间 \([l,r]\) 使得每个区间可以用 \(r-l\) 次长度为 \(2\) 的操作覆盖,代价更低,而满足这个要求的区间非常好求,对于整个区间,每次对两个数异或一个相同的数,则区间异或和不变。那么做法就一目了然了,设 \(s_i\) 表示异或前缀和,\(f_i\) 表示区间 \([1,i]\) 全部为 \(0\) 的最小代价,则:
把只和 \(j\) 相关的部分扔进字典树里就好了。
Madoka and The Corruption Scheme
1900 tag: 位运算
映射
首先默认所有层都是左边赢对答案没有影响,当改变一场比赛进程,即将一个比赛从左边改为右边,显然与现在不连通的部分改没用,一定是在当前路上改,答案就是通过改路能走到的点的个数,现在问题转化成从树根出发,默认往左走,最多往右走 \(k\) 次能走到的点的个数。
这里有一个巧妙的双射,将向左走视为 \(0\),向右走视为 \(1\),则每个位置对应一个二进制数,能走到的二进制数就是 \(\op{popcnt}(x)\le k\) 的数,直接用组合数统计就好了。
Letter Picking
1800 tag: 区间dp
简单博弈
题目中的操作是从两边取字符,故剩余的一定是原串的一个子串,可以想到区间 dp,设 \(f_{i,j}\) 表示区间内答案,值为 \(1/-1/0\) 分别表示答案为 \(\mathtt{Alice}/\mathtt{Bob}\mathtt{Draw}\) ,直接大力分讨,Alice 希望能赢则赢,否则至少平局,Bob 同理,统一一下就是 Alice 希望 \(f_{i,j}\) 值大,Bob 希望 f_{i,j} 小,直接做就好了。
Moving Both Hands
1800 tag: 分层 Dijkstra
分层 Dijkstra 模板题。
Hot Black Hot White
1800 tag: 杂题
首先模三意义下的值即各数位模三意义下值的和,所以 \(\op{concat}(x,y)\equiv x+y\pmod 3\),在通过打表计算得到原式即:
提取关键信息,当且仅当 \(3\mid x,3\mid y\) 时答案值为 \(0\),当且仅当 \(3\nmid x,3\nmid y\) 时值为 \(2\),所以直接将每个数转化成是否被 \(3\) 整除,然后若整除的数超过一半则可以避免两个不整除的数反应,否则可以避免两个整除的数之间反应,直接做就好了。
2+ doors
1900 tag: 贪心
位运算
首先因为约束是按位或运算,可以每一位分开做。
题目有一定迷惑性,如果没有字典序最小条件可以转化成 2-SAT 问题,若 \(a_i\cup a_j=x\),对于每一位 \(k\),如果 \(k\in x\) 则 \(k\notin a_i\implies k\notin a_j,k\notin a_j\implies k\notin a_i\),否则 \(k\notin a_i,k\notin a_j\),直接做就好了。
但是 2-SAT 的 Tarjan 写法不支持输出字典序最小解,这里有一个更简单的贪心写法。
首先对于每一位,如果 \(a_i\cup a_j=0\) 则两者都必然为 \(0\),否则两者中至少有一个是 \(1\),默认全为 \(1\),按照约束将该取 \(0\) 的取 \(0\),有 \(1\) 的建双向边,此时若边的一端已经有 \(0\) 了则另一端一定是 \(1\),讲这些再确定完,一个特例是如果 \(a_i\cup a_i=1\) 则说明 \(a_i=1\) 也要直接确定。
接下来如果有数还未确定就从前往后有限确定为 \(0\),然后将右边相连的部分确定为 \(1\) 即字典序最小解。
Build a Tree and That Is It
1900 tag: 杂题
首先无解情况,将只和 \(1,2,3\) 相关的部分取出,则 \(2\mid(d_{1,2}+d_{2,3}+d_{1,3})\),因为每条边都覆盖到了两次,然后若点数不够也不行,还有如果最长的比两个别的加起来还长就肯定不行(三角形不等式)。
然后就分类讨论三点是否共线就好了。
Edge Split
2000 tag: 贪心
构造
随机化
dfs树
赛后看了题解,做法大有不同。
首先从每条边分析,大部分情况下每条边会连接两个连通块,只有成环的时候才是特例,所以首先用并查集把一棵树染成红的,现在最多还剩下 \(3\) 条边。
如果三条边成环就会有问题,现在问题是如何避免环的出现。
因为我们是乱序加边,没有什么好的性质,只能暴力找边替换,时间复杂度是假的,所以有一种更“巧妙”的方法。
我们将边打乱重新做,多做几遍总会出解,毛估估随不了几遍就出答案了。
更正经的做法,上面我们讲到我们的树没有什么优美的性质,究其原因,当我们替换掉一条边以后,我们不能保证放回去的那条边不会和别的边成环,或者说不一定连通,但是我们可以找到一个更优美的树,用非树边替换掉一条特定的边一定不会成环——dfs 树。
dfs 树中任意非树边都是后向边,假如我们发现多出来的三条边成环了,我们只要将其中一条边改成其连接的深度靠下的点连向父亲的边就一定不会成环,因为一定依旧连通。
注意,dfs 树中任意一条边第一次访问到都是以后向边的形式被访问,但如果是最后一次访问到就是前向边。
Circular Mirror
2000 tag: 组合数学
首先圆上直角三角形的性质即斜边为直径,所以现将所有能构成直径的端点组都数出来,这样就将题目转化为有 \(c_1\) 个独立点和 \(c_2\) 对关联点,将它们全部染成 \(m\) 种颜色,要求如果有一对关联点被染成了相同的颜色,则别的点都不能染这种颜色。
因为将一对点染色会使这种颜色不能再用,想到枚举染了几对点,则剩下部分答案就很简单了,所有独立点都是剩下的颜色里随便染,关联点只要颜色染得不同就好了,答案即:
Cut Substrings
2100 tag: 字符串
KMP
dp
首先找出所有 \(S\) 串中 \(T\) 串的出现位置,KMP 和 Hash 都可以,然后找最小长度和计算方案数都可以 dp,我们分开讲。
首先本着求什么设什么的原则,设 \(f_i\) 表示 \(S\) 中前 \(i\) 个位置不出现完整的 \(T\) 串的最小花费,转移的时候问题就出现了,当我们遇到一个新的 \(T\) 串的时候,我们不能得知一种方案中这个 \(T\) 串的前面部分有没有被覆盖,所以多设一个 \(g_i\) 表示前 \(i\) 个位置不出现完整的 \(T\) 串且以 \(i\) 结尾的 \(T\) 串被选中的最小代价,互相转移:
求方案数类似,不过多一维设 \(f_{i,j}\) 表示 \(S\) 中前 \(i\) 个位置不出现完整的 \(T\) 串花费为 \(j\) 的方案数,\(g_{i,j}\) 同理,转移:
Field Photography
2100 tag: 位运算
首先操作是按位或,那么我们首先确定每次肯定只操作 \(2^k\) 而且因为可以分解,所以对于给定的 \(W\) 真正有用的就只有 \(\op{lowbit}(W)\),那么将所有区间按照 \(\op{lowbit}(W)\) 取模后区间覆盖分讨就好了,先预处理好所有 \(2^k\) 的结果,询问直接输出就好了。
用 map
被卡常了,改成常数更小的 sort
+ priority_queue
就过了。
Prefix Function Queries
2200 \(\bigstar\) tag: 字符串
KMP
KMP自动机
看到前缀函数非常自然地想到 KMP,第一个最暴力的想法是每次询问将串接起来跑 KMP,时间复杂度显然是 G 的,一个稍微优化一下的想法,\(S\) 先跑好 KMP,记录此事 \(\mathrm{fail}\) 指针的位置,每次做的时候直接做后面几个就好了,但是时间复杂度是假的,因为 KMP 时间复杂度基于均摊,\(\mathrm{fail}\) 往回跳的复杂度是均摊到每一次才正确。
我们需要一种构造过程不基于均摊的字符串数据结构,所以注意到 KMP 的变种——KMP 自动机。
其实就是构造一个简化版只有一个串的 AC 自动机,对于每个节点,直接记录它从每个字符走到的节点,此时单次更新是严格的 \(\mathO{|\Sigma|}\) 不基于均摊。
KMP 自动机能够解决将原串从任意位置截断都在后面重新接上一个串求 \(\mathrm{fail}\) 数组,可以看成是从这个点延伸出一条新链的字典树,AC 自动机处理,只不过因为只有一个串所以简化了。
Madoka and The Best University
2200 tag: 数论函数
既然只给了一个式子,就直接推式子:
其中 \(S_1(n)\) 表示和小于等于 \(n\) 的互质数的对数,\(S_2(n)\) 表示和小于等于 \(n\) 的互质数的和,计算这两个东西并不是很简单,先把小于等于改为等于算算看,设 \(f_1(n)\) 表示和为 \(n\) 的互质数的对数,\(S_2(n)\) 表示和为 \(n\) 的互质数的和,则:
用线性筛筛出 \(\varphi(n)\),直接算就好了。
Madoka and The Best University
2300 tag: 数论
扩欧
贪心
首先可以将用任意多黑胡椒的方案的最优解都求出来,不难发现是单峰的。
再看每次询问的式子,是丢番图方程,用扩展欧几里德解决,但是只能求得一组任意解,通过调整到最接近单峰的位置,只要再 \([0,n]\) 范围内就满足要求,在单峰前后各取一个值,比一下大小就好了,无解的情况除了丢番图方程无解还有解都不在范围内的情况。
Qpwoeirut and Vertices
2300 \(\bigstar\) tag: 整体二分
可撤销并查集
最小生成树
贪心
如果只有一次询问怎么做,非常简单,每次增加一条边用并查集维护,单次询问是 \(\mathO{n^2\alpha(n)}\) 的,可以用二分优化掉每次加一条边都要扫整个图,优化到 \(\mathO{n\log n\alpha(n)}\)。
对于多次询问,时间复杂度就 G 了,一个比较自然的想法是既然单次询问二分,多次询问可以整体二分,这个想法还有一个问题就是如何快速查询一个询问满不满足要求,即 \([l,r]\) 是否连通内,首先判断两两之间连通是非常愚蠢的,因为我们只需要判断任意相邻两点是否连通就好了,这样每次判断还是 \(\mathO{n\alpha(n)}\) 的,并没有本质性优化,整体二分中一共要判断 \(\mathO{n}\) 次的话还是 G。
既然判断什么时候 \([l,r]\) 任意相邻两点连通,可以转化成什么时候 \([l,r)\) 中所有点都和下一个数连通,满足所有条件即所有的完成时间取最大值。
那么我们转而整体二分每个点的连通时间,结束后放到 ST 表上维护就好了。
现在问题来到整体二分上,我们要保证每层的时间复杂度是与 \(\mathrm{len}\) 相关的,而不是与 \(n\) 相关的,时间复杂度才正确,但实际上当我们在判断 \(mid\) 是否合法时,我们要将 \([1,mid]\) 所有的边都用一遍,时间复杂度肯定是挂的,但我们发现每次我们做完 \([l,r]\) 开始做 \([l,mid]\) 和 \((mid,r]\) 时后两者要判断的 \(mid\) 与当前 \(mid\) 之间的差是 \(\mathO{\mathrm len}\) 的,所以用可撤销并查集,每次利用上一次的信息,时间复杂度正确。
其实有更简单的做法,将边的编号作为其权值,则问题转化为每相邻两点的最小瓶颈路,用最小生成树解决,可以用 Kruskal 得到最小生成树再跑 LCA,一个更简单的做法是利用 Kruskal 的并查集,不用路径压缩而是用按秩合并,不显示建树,此时两者在并查集上求的最小瓶颈路是等价的,可以直接暴力 LCA,因为树高只有 \(\mathO{\log n}\) 所以时间复杂度正确。
Mark and Professor Koro
2300 tag: 线段树
二分
值域并不是很大,可以将修改放到值域上做,性质会好很多,设 \(a_i\) 表示数组中 \(i\) 的数量,根据题目的要求,再增设一个 \(s\) 数组满足 \(s_i=\floor{\frac{s_i}2}+a_i\),则问题转化成询问在 \(a\) 数组里选择一个数使其值变化 \(1\),保证不会有值小于 \(0\),求 \(s\) 中有值的最后一个的位置。
单次修改暴力怎么做,在 \(a\) 上先修改,然后在 \(s\) 里会产生怎样的影响?若修改 \(a_i\),首先 \(s_i\) 会变化 \(1\),然后如果使得 \(\floor{\frac{s_i}2}\) 变化,则对后面还有影响,继续向后以同样的方式做,当然,需要优化。
这里有一步转化,将 \(s_i\) 对 \(2\) 取模,则可以将整个 \(s\) 看成一个二进制数,\(s_i\) 是这个二进制数的第 \(i\) 位则 \(a_i\) 可以看成 \(a_i\times2^i\),我们就是在处理当这个二进制数变化了一个二的整次幂后会怎么变化,那么变化的形式即进位和退位,二进制数的退进位有优美的性质,在第 \(i\) 位加一即从第 \(i\) 位向后延伸将 \(1\) 都改成 \(0\),将遇到的第一个 \(0\) 改成 \(1\),退位同理。
这样我们就可以二分看一整段是否都是 \(1\),可以用线段树维护整段的奇偶数量信息,区间加区间查。
Tree Queries (Hard Version)
2300 \(\bigstar\) tag: 构造
先看看对应的 easy version 怎么做,树的大小没这么大,\(\mathO{n^2}\) 时间复杂度可以被接受。
我们现钦定一个点为根,先询问根,然后我们就能保证对于任意一个子树,子树外部有一个点被询问道,然后树形 dp,设 \(f_x\) 表示已经确定目标点在 \(x\) 这个子树里后 \(x\) 这个子树里需要选择几个点才能确定点的位置,首先在定位到任何一个儿子的子树中后在它的子树里肯定还要定位,所以先对所有儿子的 \(f\) 求和,其次,我们要将目标点定位到当前节点的一个子树里,所以在只允许有一个子树为空,综上,转移即:
利用这个 dp 就可以在 \(\mathO{n^2}\) 时间复杂度内解决,套换根 dp 就可以做到 \(\mathO{n}\),但是有更巧妙的做法。
我们只要找到一个点在答案中,以那个点为根找到的就一定是答案,更进一步的,我们可以做一点转变,观察我们 \(f_x\) 的定义,只要能够确定目标点在当前子树中就好了,我们在树上找一个度数至少为 \(3\) 的点,设这个点为 \(x\),则以 \(x\) 为根,此时求出 \(f_x\) 至少有两个子树里有取点,就一定可以确定目标点在哪个子树里,可以直接求答案。若没有度数为 \(3\) 的点则直接以链头为询问点可以根据深度直接找到答案。
Keshi in Search of AmShZ
2300 \(\bigstar\) tag: Dijkstra
一道魔改 Dijkstra 的好题,本着求什么设什么的原则,先设 \(f_x\) 表示从 \(x\) 走到终点需要的天数,因为求最大天数,所以设必定走向最劣的点,即 \(f_y\) 最大的点,初始 \(f_n=0\),然后从 \(f\) 小的向大的转移,假设要让 \(f_x\) 一定走向 \(f_y\),则要把 \(f_x\) 能走到其它点的路都堵死,则设 \(x\) 的度数为 \(\mathrm{deg}_x\),则转移 \(f_x=f_y+\mathrm{deg}_y\),若强制走最优的 \(k\) 种走法,则答案为 \(f_x=(\min_{S\subseteq\op{son}(x),|S|=k}\max_{y\in S}f_y)+\mathrm{deg}_x-k+1\),那么我们可以用 Dijkstra 来维护转移,每转移一次就让度数减一表示允许的走法多了一种,需要堵死的边少了一条。
Number of Groups
2300 tag: 并查集
非常非常典的题,区间有交则一定是一个区间包含另一个区间的左端点的形式,所以每种颜色按左端点排序,用一个并查集维护答案集合,再用一个常用的技巧,因为发现在同一集合内的同色区间按左端点排序后一定是连续的一段,故再用一个并查集维护与该点同集合的左端点最右在哪里,就可以将暴力建边优化掉了。
Sorting Pancakes
2300 tag: dp
没有任何技巧的暴力 \(\mathO{n^3}\) dp,设 \(f_{i,j,k}\) 表示到第 \(i\) 个为止,现在有 \(j\) 块松饼需要往后移(若 \(j<0\) 则表示有 \(-j\) 块松饼需要往前移),第 \(i\) 个盘子里松饼数大于等于 \(k\),转移可以根据状态直接得到。
388535 (Hard Version)
2300 tag: XOR
构造
一道巧妙的构造题。
先看看对应的 easy version 怎么做,因为是连续的 \(0~r\) 所以有一个良好的性质,就是任何一位上 \(0\) 都比 \(1\) 多,所以用这个性质可以直接求解,若任何一位上 \(0/1\) 数量相同则这一位上填 \(0/1\) 无所谓。
hard version 没有这个性质,所以只能利用利用异或的性质构造一个解。
能利用的性质不多,但并不是没有,比如可以利用异或两两对应的关系,easy version 中讲到若任意一位上 \(0/1\) 数量相同则这一位填 \(0/1\) 无所谓,这里也是一样的,这个性质最为重要,我们每次观察 \(l,r\) 的奇偶性,若 \(l\) 为偶数且 \(r\) 为奇数,则 \(r-l+1\) 个数可以两两分组,每组内部都是异或 \(1\) 的关系,即末尾 \(0/1\) 数量相同,我们可以忽略末位,反复做这个动作直到不满足要求。
依旧按照异或 \(1\) 分组,则没有另一组的只可能是 \(l,r\) 中的至少一个,且原来没有分组,在异或完 \(x\) 后一定也没有分组,找到给定的 \(a\) 序列中哪个没有分组,没有分组的数的数量一定和原来一样,所以暴力判断是否满足所有数异或完都在 \([l,r]\) 内就好了。
Almost Perfect
2400 tag: 组合数学
首先观察题目中对排列的要求,得到最重要的性质。
约束的形式是第 \(i\) 个位置的值和值 \(i\) 的位置的差,位置和值的关系联想到置换环,用 \((a,b,\cdots)\) 来表示由 \(a,b,\cdots\) 组成的置换环(先不关注顺序),我们可以来推一下性质:
设 \(p_i=j\),则根据约束 \(|p_j-i|\le1\),那么 \(p_j\in\{i-1,i,i+1\}\),若 \(p_j=i\),则对于 \(i\) 满足 \(p_i=p^{-1}_i=j\),故满足要求,一个特例是 \(p_i=i\),这样就已经有两种情况 \((i),(i,j)\)。
再分讨其他两种情况,两种情况类似,若 \(p_j=i+1\),则 \(p_{i+1}\in\{j-1,j,j+1\}\),\(j\) 用过了所以再分讨,若 \(p_{i+1}=j+1\),则 \(p_{j+1}={i,i+1,i+2}\),此时 \(i+1\) 用过了,若 \(p_{j+1}=i+2\),则 \(p_{i+2}\in\{j,j+1,j+2\}\),而 \(j,j+1\) 都用过了,所以只能一直变大无法变成置换环,故只能 \(p_{j+1}=i\),可得置换环为 \((i,j,i+1,j+1)\),别的分讨的情况都可以转化为这种情况,而这个置换环的顺序有两种:\(i,j,i+1,j+1\) 和 \(i,j+1,i+1,j\)。
接下来就是用这三种置换环覆盖整个排列,可以用组合数学统计。
首先枚举一共有几个四元置换环,若有 \(k\) 个置换环,则因为四元置换环的形式为 \((i,j,i+1,j+1)\),问题转化为在 \([1,n)\) 中选出 \(2k\) 个元素满足两两不相邻,在长度为 \(n\) 的序列中选出 \(m\) 个不相邻的元素是非常经典的模型,先从序列中取走 \(m-1\) 个元素,再随意取 \(m\) 个元素,再在每个元素之间放回去,则方案数 \(\binom{n-m+1}m\),那么在 \(n-1\) 个元素中选出 \(2k\) 个元素互不相邻就是 \(\binom{n-2k}{2k}\),接下来是分组,将 \(2k\) 个元素分成 \(k\) 个二元组,本来二元组是无序的,但是注意到前面讲到一组 \(i,j\) 可以有两种排法,所以可以当作二元组有序,那么分组方案就是 \(\frac{(2k)!}{k!}\)(先将所有 \(2k\) 个元素全排列,再两两捆绑,对于每种分组方案,在排列中出现 \(k\) 次)。
设将大小为 \(i\) 的排列用一元环和二元环覆盖的方案数为 \(f_i\),运用递推求解,考虑第 \(i\) 个元素,可以跟自己构成一元环,剩下的 \(i-1\) 个元素方案数 \(f_{i-1}\),也可以和前面 \(i-1\) 个构成二元环,剩下 \(f_{i-2}\),故递推式为:
答案式为:
Lemper Cooking Competition
2400 \(\bigstar\) tag: 前缀和
先挖掘这个操作的性质,将这个操作写成加减的形式,即选定一个 \(i\),使得 \(a_{i-1}\gets a_{i-1}+a_i,a_i\gets a_i-2a_i,a_{i+1}\gets a_{i+1}+a_i\),总和不变。
这里有一个非常奇妙的思想火花,就是观察前缀和数组的性质(NOIP2021T3 考差分数组性质不会,这题前缀和性质还是不会)。
手模发现这个操作等价于在前缀和数组中交换 \(i\) 和 \(i-1\) 位置,原数组非负即前缀和数组单调不减,因为不能交换 \(n\) 和 \(n-1\),所以首先如果前缀和数组中有负数或 \(a\) 数组的和不是前缀和最大值都无解,否则就是冒泡排序交换次数,直接归并求逆序对。
Tonya and Burenka-179
2400 tag: 杂题
首先想到这个跳的过程肯定有循环节,否则就是选中所有 \(n\) 个元素,若每次参数为 \((s,k)\),先发现 \(k\) 和 \(\gcd(n,k)\) 是等价的,因为最终都是每隔 \(\gcd(n,k)\) 选中一个直到形成循环,则 \(k\) 只要取 \(n\) 的因数就好了,\(s\) 也只要取 \([1,k]\) 范围内的数,实际答案就是 \(k\sum_{k\mid(i-s)}a_i\)。
那么就可以暴力维护所有 \(n\) 的因数作为 \(k\),然后每个 \(s\) 的答案,然后每个 \(k\) 内部每个 \(s\) 的答案和全局答案各用一个 multiset
维护,每次修改暴力在每个 \(k\) 内部找到对应的 \(s\) 修改,单次修改时间复杂度 \(\mathO{\sum_{d\mid n}\log d}\),需要优化。
这里运用贪心的思想,将绝对不可能构成最优解的答案去除,如若有两个 \(k_1,k_2\) 满足 \(k_1\mid k_2\),我们可以证明 \(k_1\) 对应的任何一个 \(s\) 都不可能构成最优解,因为将 \((s_1,k_1)\) 选中的 \(a_i\) 取出来,最后对它们求和,而我们可以在其中选取一组模 \(\frac{k_2}{k_1}\) 意义下最大的一组,然后令 \(s_2\) 正好取这一组,此时最终结果肯定是 \((s_2,k_2)\) 更优。
由此可得我们只需要维护互相不为倍数的一组 \(n\) 的因数就好了,则这些数的集合为 \(\{\frac np\mid p\in \mathbb P,p\mid n\}\),即 \(n\) 除以它的一个质因数的结果,这些数互相没有倍数关系,且任意一个 \(n\) 的因子(题中禁止 \(k=n\))都是其中一个数的因数,单次时间复杂度就优化到 \(\mathO{\sum_{p\in\mathbb P,p\mid n}\log\frac np}\) 因为质因子个数是 \(\mathO{\log n}\) 的,所以时间复杂度肯定是 \(\mathO{n\log^2 n}\) 的。
Long Way Home
2400 \(\bigstar\) tag: Dijkstra
斜率优化dp
李超树
先思考只能飞一次且只能以飞结尾的答案,先跑一遍 Dijkstra,得到走路到每个点的最短路,然后现在问题变成飞的最优解,则用 dp 的思想,设 \(d_i\) 表示走到 \(i\) 的最短路长度,\(f_i\) 表示以飞结尾的最短路,先写出转移再进行变形分离:
这是一个斜率优化非常经典的式子,可以用单调队列直接维护,但是弹队头和队尾的式子会有些麻烦,所以有更暴力的办法——因为是全局转移,所以直接用李超树维护。
这样就解决了以飞结尾的最短路问题,那么问题就是飞完以后还要再走一段,我们可以将前面的一整个 \(f_i\) 看成从原点“飞”到了 \(i\),这样的话我们直接维护一个超级源点,向每个 \(i\) 连一条权值为 \(f_i\) 的边,这样从超级源点再跑一遍 Dijkstra,就得到了答案。
实际上多飞几次的作法是一样的,将重复前面的过程就好了,时间复杂度 \(\mathO{k(n\log n+m\log m)}\)
DFS Trees
2400 \(\bigstar\) tag: 最小生成树
因为题目中每条边的长度相同,所以最小生成树唯一,先求出来。
观察什么情况下 dfs 树不是最小生成树,任意一条非树边与连接这条边两端点的链取出来,不难发现这个环内只有从这两个点开始才能够正常地遍历整个环,否则必然错误,拓展得到只能从这两个点进入这个环,否则一定也不行,那么设这条非树边为 \(\lang x,y\rang\),则对于这条边,只有以 \(x\) 为根时 \(y\) 的子树和以 \(y\) 为根时 \(x\) 的子树里满足要求,利用 dfs 序和树状数组处理一下就好了。
Chopping Carrots (Hard Version)
2400 tag: 数论分块
分块
easy version 做法显然,因为答案的形式是 \(\max(\cdots)-\min(\cdots)\),所以想到固定最小值求最大值的方法,因为要让极差最小,就是令 \(\min\left(\floor{\frac{a_i}{p_i}}\right)=t\),让所有 \(\floor{\frac{a_i}{p_i}}\) 取至少为 \(t\) 的最小值,那么不难想到令 \(p_i=\floor{\frac{a_i}t}\),令 \(a\) 中的最小值为 \(m\) 则因为 \(t\in[1,m]\),所以可以暴力枚举,时间复杂度 \(\mathO{n|V|}\),最终答案即:
观察式子中同时与 \(a_i\) 和 \(t\) 的部分,即 \(\floor{\frac{a_i}t}\),利用数论分块的结论, \(\floor{\frac{a_i}t}\) 只有 \(\sqrt{a_i}\) 种取值,所以每次重新计算是没有必要的,我们可以用一个类似邻接表的结构维护每个 \(t\) 处会修改的 \(\floor{\frac{a_i}t}\) 有哪些,然后就是要用一个数据结构维护最大值,一个允许加入删除以及求最大值的数据结构。
首选是 multiset
或者用 priortiy_queue
实现的可删堆,但是这题把空间卡死了,所以不行,因为值域不大,可以使用树状数组上二分实现,这样的话总时间复杂度 \(\mathO{|V|\sqrt{|V|}\log|V|}\)。
但是这样的时间复杂度是很危险的,继续观察我们的数据结构需要维护什么,一个支持单点修改以及查询最大值的数据结构,比较特别的,修改共 \(\mathO{|V|\sqrt{|V|}}\) 次,而询问只有 \(\mathO{|V|}\) 次,所以既然修改与查询的次数不平衡,我们就可以用分块实现平衡时间复杂度,总时间复杂度 \(\mathO{|V|\sqrt{|V|}}\)。
Dog Walking
2400 tag: 贪心
思路平凡而巧妙。
先做题意转化,给定一个有空位的数组,允许你在空位中填 \([-k,k]\) 范围内的数,要求数组的和为 \(0\),使前缀和极差最大。
注意到 \(n\le3000\),理所当然地想到了 \(\mathO{n^2}\) 做法,那么因为前缀和极差一定是原数组一段区间的和,可以直接枚举区间的两个端点,再 \(\mathO{1}\) 计算该区间内的答案,分两种情况讨论:
- 区间和为正,则一个贪心的想法使得区间内所有空位填 \(k\),此时有可能使数组的和无法为 \(0\),那么外面能制造的最小区间和就是里面的最大区间和,所以求外面空位全填 \(-k\) 的答案就好了。
- 区间和为负,同理,将情况反一下就好了。
Empty Graph
2000 tag: 贪心
构造
一个相对而言比较巧妙的贪心构造题。
先思考如果给你一张图问你图的直径怎么做,再分解问题,问你两点之间距离你如何快速计算,令 \(x<y\) 则 \(x,y\) 间距离为 \(\op{dist}(x,y)=\min\left\{\min_{i=x}^ya_i,2\min_{i=1}^na_i\right\}\),根据式子那么图的直径只会出现在相邻的两点之间,答案就是:
接下来关注修改,不难想到既然改就都改到 \(10^9\),既然答案有一个瓶颈是全局最小值的两倍,一个粗劣的贪心是每次修改全局最小值,感觉挺对的,但实际上有些细节还是有些问题。
若要答案一定取得到全局最小值的两倍,则一个方法是将相邻两个数都改成 \(10^9\),那么我们先修改 \(k-1\) 次全局最小值,留下一次放在已经修改过的点旁边就一定能取到全局最小值的两倍,但是一个特殊情况是 \(k=1\),则此时我们只能退而求其次,修改全局最大值旁边的点,答案就是 \(\min\left\{2\min_{i=1}^na_i,\max_{i=1}^na_i\right\}\)。
还没完,我们再做一次将全局最小值修改掉的操作,再直接计算整张图的直径,与刚刚的答案取最大值,这样就涵盖了全部答案,可以分类讨论证明。
Kingdom of Criticism
2500 tag: 并查集
先想想如果没有 \(1\) 操作该怎么做,如果没有 \(1\) 操作,则显然初始颜色相同的两个点颜色不可能会变得不同,不难想到只关注颜色集合,每次 \(3\) 操作 \(l,r\) 即将 \(\left[l,\frac{l+r-1}2\right]\) 全部合并到 \(l-1\),将 \(\left[\frac{l+r+1}2,r\right]\) 合并到 \(r+1\),可以用并查集维护,而且不难发现一个集合被合并以后就消失了,那么每次 \(3\) 操作扫过的集合都会消失,并会新建最多两个集合,可以用 map
维护,时间复杂度正确。
有了 \(1\) 操作也不难,我们对并查集做一些拓展,我们只要始终保证我们的每个点都是叶子节点,这样直接更改它的父亲就不会对别的点产生影响。
Zeros and Ones
2500 \(\bigstar\) tag: 数位dp
位分治
先对题意进行转化,令 \(f(x)=\op{popcnt}(x)\bmod2\),求 \(\sum_{i=0}^{k-1}[f(i)\ne f(i+n)]\)。
可以再简化一下就成了 \(\sum_{i=0}^{k-1}f(i\oplus i+n)\),那么关注到 \(m\) 特别大,且 \(f(x)\) 是可以按位分解贡献的,所以可以用数位 dp 的思路。
这题的数位 dp 非常特别,是从低到高处理的,这也可能是因为在本题中有一个 \(i+n\),而在从高位到低位的过程中对于进位的处理并不容易,相反在从低位到高位的过程中处理就相对容易,但是在从低位到高位做的过程中还有另一个问题,就是如何处理比上界更大的问题,我们可以直接多开一维来存,所以设 \(f_{i,0/1,0/1,0/1}\) 表示从小到大做到第 \(i\) 位,前 \(i\) 位是否比上界大,是否有进位,当前 \(\op{popcnt}\) 的奇偶性,分讨一下就好了。
还有一个非常高妙的位分治做法,你画 \(k\) 张无限大的 \(0/1\) 表格,行和列都从 \(0\) 开始,第 \(k\) 张表格第 \(i\) 行第 \(j\) 列表示 \(i\oplus j\) 的第 \(k\) 位,那么答案查询的位置就对应表格中的一个正方形的对角线。我们可以一张一张考虑每一个表格。
首先观察每张表格的情况(.
表示 \(0\),#
表示 \(1\)):
.#.#.#.#
#.#.#.#.
.#.#.#.#
#.#.#.#.
.#.#.#.#
#.#.#.#.
.#.#.#.#
#.#.#.#.
..##..##
..##..##
##..##..
##..##..
..##..##
..##..##
##..##..
##..##..
....####
....####
....####
....####
####....
####....
####....
####....
我们首先分析第 \(0\) 张表格,若先不看第 \(0\) 张表格,我们可以将所有表格的大小缩小两倍,此时我们发现我们回到了最开始的问题,说白了,我们可以用位分治递归的思想解决这个问题。
那么我们只要直到怎么处理第 \(0\) 张表格就好了,此时我们观察在直接缩减表格大小时还会出现一些问题,首先我们把 \(m\) 为奇数的情况排除,只要将对角线尾部给取出来就好了,这样就确保了 \(m\) 为偶数,那么在做 \(\frac m2\) 的情况,此时我们发现我们的对角线经过的第 \(1\) 张表格的情况分成两种,第一种是访问的颜色全部相同,一种是 \(0/1\) 相间访问,正好对应 \(n\) 为偶数和奇数,我们直接分类讨论,。
- 若 \(n\) 为偶数,对应第 \(0\) 张表格中对角线全为 \(0\),在第 \(1\) 张表格中对应访问的颜色完全相同,答案可以直接由减半的情况直接推得。
- 若 \(n\) 为奇数,对应第 \(0\) 张表格中对角线全为 \(1\),在第 \(1\) 张表格中 \(0/1\) 相间,可以分别调用第一张表格中为 \(0\) 的部分和为 \(1\) 的部分,求得答案。
设 \(\op{solve}(n,k)=\sum_{i=0}^{k-1}f(i\oplus i+n)\),则递归式:
时间复杂度证明引用一个经典结论,对 \(f_n\) 记忆化搜索后 \(f(n)=f\left(\floor{\frac n2}\right)+f\left(\ceil{\frac n2}\right)\) 最多只可能访问到 \(\mathO{\log n}\) 个节点。
Journey
2500 tag: 树形dp
求两种情况的最小值,第一种是两条仅有一个节点相交的链一倍代价,其余边两倍代价,另一种是两条不相交的链一倍代价以及两条链之间的一条边无代价,其余边两倍代价。一眼就是树形 dp,而且是非常恶心那种。现在问题就是怎样让树形 dp 写得方便一些。
设 \(f_{x,k,0/1}\) 表示 \(x\) 的子树内,已有至多 \(k\) 条直链一倍代价,是否选过一条边的无代价,其它边两倍代价的最小答案,其中 \(k\in[0,4]\)。这里的“\(k\) 条直链”是重点,可以大幅度减少码量,其中直链表示一条祖先到子孙的链,两条直链就可以构成一条普通链,当 \(2\nmid k\) 时,表示有一条直链的链头在 \(x\) 上,此时我们发现转移可以直接将 \(k\) 相加,而如果对于 \(k=2\),转移时可以使当前这条边无代价,不难发现所有的情况都会被得到。
FTL
2400 tag: dp
题目背景 neta 了游戏《超越光速(Faster Than Light)》
关注题目特殊信息,首先保证了每炮至少打出一点伤害,其次,血量、护盾、伤害都只有 \(5000\),而冷却都是 \(10^{12}\) 级别的,所以暗示我们使用 dp,以血量为状态。
设 \(f_{i}\) 表示造成了 \(i\) 点伤害花费的最小时间,特别的,我们钦定对于任意状态时间,两门炮都是刚放完炮的状态(同初始情况)。
既然每炮至少打出一点伤害,说明最多游戏会进行 \(5000\) 轮,那么我们对于一个状态,直接枚举接下来哪门炮放,放几轮,可以计算出另一门炮放几轮,同时我们注意到最后一炮同时放肯定能造成更大的伤害,所以直接转移即可,且最后一炮一起放也正好符合我们状态的定义,最优性显然。
Good Subarrays (Hard Version)
2500 tag: dp
easy version 没有修改,观察题目性质发现对于每个点其左端点的限制更加好找,故对于每个点求出以其为右端点左端点的方案数,设以 \(i\) 为右端点的方案数为 \(f_i\),则:
hard version 每次询问有一个独立的临时修改,延续 easy version 的思路,考虑每次修改的影响。
观察 dp 转移的式子,对于每个 \(i\),一定能找到一个 \(k\) ,满足 \(f_i=f_k+i-k\),即对于每个 \(i\) 我们都能找到一个限制住其的瓶颈。
现在对于一个修改,观察其对原 dp 的影响,首先找到其后面第一个瓶颈,即满足修改后最小的 \(k\) 满足 \(k>i\) 且 \(f_k=a_k\),那么这个数及其后面都不会变化,修改位置之前也不会变化,中间的部分瓶颈一定是修改位置修改后的值,是一个公差为 \(1\) 的等差数列,可以直接算。
关键就是这个瓶颈,首先我们思考怎样的 \(i\) 更容易称为瓶颈,不难发现满足 \(a_i-i\) 更小的 \(i\) 会优先称为瓶颈,那么我们将所有询问与初始的 \(a_i\) 全部离线下来按照 \(a_i-i\) 从小到大排序,这样先被枚举到的一定是瓶颈。
将原 \(f\) 数组的前缀和记录下来,注意后缀和数组一定要在离线过程中计算,不能直接计算,因为这里的后缀和数组是不能受到前面影响的,即默认前面没有限制后缀部分的瓶颈,然后用前后缀和中间部分的等差数列拼出答案即可。
House Planning
2400 tag: 构造
贪心
首先不难发现两待定点之间的距离一共有 \(2n\) 中可能,即 \(d_{1,1}\) 和任意一个 \(d_{2,i}\) 匹配确定是和或者差。
那么我们可以先枚举距离,再确定 \(d\) 之间的两两匹配。
一个比较巧妙的贪心,取出当前 \(d_1\) 和 \(d_2\) 未匹配的最大值,因为是最大值,所以如果再向离另一个待定点远方向延伸一定无法匹配,所以延伸方向确定,用 multiset
维护,直接匹配即可。
Balance (Hard version)
2400 \(\bigstar\) tag: 暴力
复杂度均摊
一个看似暴力实则复杂度正确的解法。
首先是 easy version,一个最暴力的想法是每次询问 \(x\) 暴力判断 \(x,2x,\cdots\) 直到 \(kx\) 不在集合中出现。
基于这个暴力去优化,一个非常简洁的优化,因为只有加操作,对于同一个询问的 \(x\),答案随时间增加只增不减,所以将每个 \(x\) 对应的答案记下来再次询问时直接继续往后扫就好了。
这就是 easy version 的正解,大约最劣情况即插入和询问都为 \(1\sim,n\),则时间复杂度即 \(\mathO{n\log n}\)。
对于 hard version,增加了一个删除操作,延续 easy version 的思路,我们只思考删除操作对答案的影响,首先删除的数没有贡献的数肯定删除也没有影响,对于有贡献的询问,直接记录一个 multiset
表示当前被删掉的数的集合,如果这个集合为空,则为当前答案继续往后像 easy version 一样做,否则直接输出集合中的最小值,时间复杂度毛估估也是 \(\mathO{n\log n}\) 级别的(具体证明不会)。
The LCMs Must be Large
2100 \(\bigstar\) tag: 位运算
构造
非常高妙的结论证明。
首先可以发现,如果朵拉两次选的集合不交则一定无解,这是一个显然的充分条件,但是出乎意料的,这是一个充要条件,我们可以通过构造证明其必要性,即证明只要集合两两有交就有解。
对于每次询问,我们为其赋一个两两不同的质数,然后令每个商店卖的数为所有包含它的询问对应质数的积,则因为所有询问两两有交,每次询问到的数的 \(\lcm\) 一定是所有数的 \(\lcm\),而每次询问的补集正好缺少这个询问对应的质数,故得证。
那么判定两两有交时间复杂度为 \(\mathO{\frac{nm^2}\omega}\)。