2022 联赛做题记录 3.0
NOIp2022 rp++!
10.30
[CSP-S 2022] 星战(随机化 + 哈希)
这两个条件可以表示为:
- 从任意一个点出发都可以到达一个环上。
- 每个点的出度为 \(1\)。
实际上第二个条件包含了第一个条件。因为满足第二个条件的图就是一个基环内向树森林。
我们给每个点记录一个随机权值 \(val_i\),同时记录 \(s_i\) 表示指向 \(i\) 的所有还存在的边中所有入点的权值和,\(t_i\) 表示指向 \(i\) 的所有不存在的边中所有入点的权值和,\(sum=\sum s_i\)。\(s_i,t_i,sum\) 都可以实时维护。
那么一张图满足条件当且仅当 \(sum=\sum val_i\)。
模拟这个过程即可。
代码:https://pastebin.ubuntu.com/p/2VFvhCdX3X/。
[CSP-S 2022] 数据传输(DP + 点分治 + 矩阵)
\(k=1\) 就是路径权值和。
\(k=2\) 只会走路径上的点,设 \(dp_{i,0/1}\) 表示走到了 \(i\) 的前驱 / \(i\),要花费的最小代价,转移可以写成 \((\min,+)\) 形式的矩阵。
\(k=3\) 可能会向外走一个点,记 \(val_i\) 表示与 \(i\) 相连的点中权值最小的点,设 \(dp_{i,0/1/2/3}\) 表示走到了 \(i\) 的前驱、离 \(i\) 距离为 \(1\) 的点、点 \(i\)、\(i\) 的后继的最小代价。转移有:
注意到算 \(dp_{i,3}\) 的时候我们并不知道后继节点的贡献,所以我们在它转移到 \(dp_{i,2}\) 的时候再计算。
同样可以把转移写成 \((\min,+)\) 矩阵乘法。点分治处理当前子树内的询问,记录每个点向上 / 向下的矩阵乘积即可。
因为第一个点一定要选,所以记录向上的矩阵的时候需要把 \(dp_{i,1}\) 的转移系数和 \(dp_{i,3}\) 单独加上起点的权值。
细节可能有点多……
代码:https://pastebin.ubuntu.com/p/f3g97RnX9j/。
[IOI2020] 嘉年华奖券(贪心 + 构造)
考虑在每一轮抽奖中,得到的奖励数额就是前 \(\frac{n}{2}\) 大的数字之和减去前 \(\frac{n}{2}\) 小的数字之和。
不难发现选 \(k\) 轮的过程就相当于在每一种颜色的奖券中选择 \(k\) 张,并且让所有选出来的奖券的前 \(\frac{nk}{2}\) 大有正贡献,前 \(\frac{nk}{2}\) 小有负贡献。
假如我们已经选完了这 \(nk\) 张奖券,那么有结论:一定存在一种方案使得前 \(\frac{nk}{2}\) 大有正贡献,前 \(\frac{nk}{2}\) 小有负贡献。
证明考虑如果不存在,那么对于任意一种方案,肯定会有一种颜色中有一张负贡献的权值比正贡献的权值大,这个时候交换它们答案会更优。
接下来考虑怎么选择奖券。我们先让每种颜色的前 \(k\) 小取到负贡献,然后进行 \(\frac{nk}{2}\) 轮替换操作:即每次选择替换后答案变化最大的颜色(也就是 未被选择的最大值 与 最大的负贡献 之和最大的颜色),将其未被选择的最大值取到正贡献,同时将其最大的负贡献清除。手玩一下可能更好理解。
对于分配,我们将颜色按照取到正贡献的数量从大到小排序依次考虑是在第几轮被放入。不难发现考虑剩余空位最多的若干轮是最优的。
写的可能有点混乱/yun
代码:https://pastebin.ubuntu.com/p/PRP6NS77wj/。
2022.10.31 考试 T1 T2 T3 T4
11.1
2022.11.1 考试 T1 T2 T3
[ARC108E] Random IS(区间 DP + 树状数组 + 概率期望)
区间满足可合并性,考虑区间 DP。
先在序列开头补上 \(a_0=0\),结尾补上 \(a_{n+1}=n+1\)。
设 \(f_{i,j}\) 表示选了 \(a_i\) 和 \(a_j\),在区间 \([i+1,j-1]\) 中选择若干个数的期望数量。
设 \(cnt_{i,j}=\sum\limits_{k=i+1}^{j-1}[a_i<a_k<a_j]\),转移有
倒序枚举 \(l\),然后正序枚举 \(r\),维护 \(n+2\) 个树状数组,分别维护 \(cnt\)、\(f_{i,*}\) 和 \(f_{*,j}\)。
时间复杂度 \(\mathcal{O}(n^2\log n)\)。
代码:https://pastebin.ubuntu.com/p/hj8PnSFN7w/。
[洛谷 P5391] [Cnoi2019] 青染之心(背包 + 树链剖分优化空间)
暴力 DP 时间复杂度可以接受,但是空间开销太大。
考虑我们可以建一棵操作树出来。然后在这上面树剖。
因为只有 \(\mathcal{O}(\log n)\) 条重链,所以可以设 \(f_{i,j}\) 表示 DP 到该节点到祖先的第 \(i\) 条重链时,背包容量为 \(j\) 的最大价值。转移时先转移到轻边,再走该重链上的点。
代码:https://pastebin.ubuntu.com/p/5Rg72btmsS/。
[COCI2017-2018#5] Pictionary(树链剖分 + 并查集 + ST 表)
第 \(i\) 天会使得 \(\gcd(a,b)=m-i+1\) 的所有点连通,我们不妨看作将所有 \(m-i+1\) 的倍数和 \(m-i+1\) 合并到一个连通块中。
那么从每一天开始枚举,尝试将 \(m-i+1\) 和它的每一个倍数连边(如果已经在一个连通块中就不连边)。答案就是树上两点间编号最小的点。树剖 + ST 表可以做到单 \(\log\)。
似乎可以证明两点的 LCA 就是编号最小的点,求一遍 LCA 就行了。
代码:https://pastebin.ubuntu.com/p/yrNV39bVhc/。
[ARC108F] Paint Tree(直径的性质)
首先拿出直径的两个端点 \(L,R\)。
如果这两个点同色,那么贡献就是直径的长度 \(D\),方案数为 \(2^{n-2}\times 2=2^{n-1}\)。
否则,不妨设 \(L\) 染成了白色,\(R\) 染成了黑色,最后方案数 \(\times 2\) 即可。
由直径的性质可知,一定有一个白点到 \(L\) 的距离为 \(x\),一定有一个黑点到 \(R\) 的距离为 \(y\)。
设 \(p_i\) 为点 \(i\) 到 \(L\) 的距离,\(q_i\) 为点 \(i\) 到 \(R\) 的距离。那么一种染色方案的贡献就相当于:每个点从 \(p_i\) 和 \(q_i\) 中选择一个出来,选择的数中的最大值就是贡献。
枚举 \(x\),考虑答案 \(\le x\) 的选择方案数。不难发现如果 \(\exist i,\min(p_i,q_i)>x\),那么方案数为 \(0\)。否则设 \(cnt\) 为 \(\max(p_i,q_i)\le x\) 的 \(i\) 的数量,方案数就为 \(2^{cnt}\)。
实现的时候可以用差分算出答案 \(=x\) 的方案数。
代码:https://pastebin.ubuntu.com/p/kbwhDjPjyh/。
[AGC001C] Shorten Diameter(直径的性质)
数据范围很小,考虑枚举。
枚举直径的中点 / 中间的那条边,然后统计距离它 \(\le \frac{k}{2}\) 的点的数量。
不难发现这样肯定是对的,因为统计非直径中点的时候肯定不优。
这种直接枚举的题目考虑枚举到无效信息是否不比答案优,如果是的就直接枚举得了。
代码:https://pastebin.ubuntu.com/p/Nk47ZGr6Cf/。
[ARC064F] Rotated Palindromes(组合计数)
如果一个序列满足条件,那么它肯定有至少一个循环移位是回文的。
直接统计不太好做,考虑对每一个回文串计算它循环移位能得到多少个不同的串。
*看到循环移位,需要想到周期!!!
枚举最小周期 \(x\),那么 \(x\) 肯定是 \(n\) 的一个约数。考虑容斥减掉还有比 \(x\) 小的长度的周期的数量。
即记 \(cnt_x\) 表示最小周期为 \(x\) 的字符串数量,那么 \(cnt_x=k^{\lceil\frac{x}{2}\rceil}-\sum\limits_{y|x}cnt_y\)。
接下来考虑循环移位能得到的串的数量。如果循环节是奇数,那么把整个循环节挪到后面去都不会算重,贡献为 \(cnt_x\times x\);否则如果循环节是偶数,就只能把前一半挪到后面,因为再挪的话就会算重,所以贡献为 \(cnt_x\times \frac{x}{2}\)。
代码:https://pastebin.ubuntu.com/p/dVytf7fHWM/。
11.2
[SCOI2007] 压缩(区间 DP)
看到划分区间,并且区间满足可合并性的问题,可以尝试使用区间 DP。
因为划分和 M 的出现有关,所以尝试把 M 设进状态。又因为串的左边默认有了一个 M,所以可以比较自然地想到下面的状态设计:
- 设 \(f_{i,j}\) 表示在 \(i\) 的左边有一个
M,区间 \([i,j]\) 最少被表示成多少个字符。
但显然,划分中间状态的时候,如果串内已经有了 M,就不能从正中间劈开,即形如 MxxxxRR 的转移可能是不行的。于是改进一下状态,即:
- 设 \(f_{i,j,0/1}\) 表示在 \(i\) 的左边有一个
M,区间 \([i,j]\) 内有无M出现,最少被表示成多少个字符。
转移是很方便的:
- 若 \([i,j]\) 长度为偶数,且 \([i,j]\) 的前半部分和后半部分相等,那么 \(f_{i,j,0}\leftarrow f_{i,mid,0}+1\)。
- \(f_{i,j,1}\leftarrow f_{i,k,0/1}+f_{k+1,j,0/1}+1\),表示在某个位置的前面放一个
M。 - \(f_{i,j,0/1}\leftarrow f_{i,k,0/1}+j-k\),表示在某个串之后原封不动地加上若干字符。
答案即为 \(\min(f_{1,n,0/1})\)。
代码:https://pastebin.ubuntu.com/p/54Cy5KRjy6/。
[2021 ICPC Asia Nanjing Regional Contest(XXII Open Cup, Grand Prix of Nanjing)] Paimon's Tree(树形 DP + 区间 DP)
题意:
有一个有 \(n + 1\) 个点的树,初始每条边没有边权,每个点都是白的。
另外给出一个长度为 \(n\) 的序列 \(a_1 , \dots , a_n\),你初始从树上选择一个点染黑,然后执行下列操作 \(n\) 次:
- 选中一条连接一个黑点和一个白点的边。
- 假如这是第 \(i\) 次操作,将这条边的权值赋为 \(a_i\)。
- 将这条边连接的白点染黑。
这样你就得到了一个带边权的树,请最大化它的直径。
数据范围:\(n \le 400\)。
对于树上问题,如果没有思路可以先考虑在序列(链)上怎么做。本题中就是考虑选定了树上的一条路径,如何最大化它的长度。
因为问题是扩展区间状物,所以考虑区间 DP,设 \(f_{i,j,k}\) 表示确定了链上的点 \([i,j]\) 之间的边,已经放了 \(a_1\sim a_k\) 的最大路径长度。
容易发现在 \([i,j]\) 之间的点的子树中且不在链上的边对答案没有影响(数量可以直接 dfs 预处理),我们称这些没有贡献的边为“垃圾桶”。
转移有:\(f_{i,j,k}\leftarrow \max(f_{i+1,j,k-1},f_{i,j-1,k-1})+a_k\);\(f_{i,j,k}\leftarrow f_{i,j,k-1}\)(\(a_k\) 可以被扔进垃圾桶)。
接下来考虑扩展问题到树上。一个显然的想法是直接把状态设计搬过来,但是我们并不知道两端点垃圾桶的大小(换句话说就是:扩展之后垃圾桶的大小可能和之前状态中转移的大小不一样),考虑多加几维解决这个问题。设 \(f_{i,j,k,0/1,0/1}\) 表示 \(i\to j\) 的路径中已经确定了前 \(k\) 个元素,\(i\) 和 \(j\) 分别是否还能扩展的最大路径长度。也就是说,设路径为 \(i\to i'\to\cdots\to j'\to j\),那么状态就表示当前路径是 \(i/i'\to j/j'\),下一步会向 \(i/j\) 扩展。
可以使用记忆化搜索完成转移。
代码:https://pastebin.ubuntu.com/p/NQ8qJYjnnR/。
[ARC080E] Young Maids(贪心 + 优先队列 + 线段树)
对于这种最小化字典序的问题,考虑 fix 一段前缀,在这个基础上去考虑问题。
将操作反过来考虑,容易发现可以把这个操作和括号序列类比,每次只能取当前最外层的括号。
那么我们就可以对于每段区间找到可选择的所有括号对中字典序最小的,也就是在奇偶性和左端点相同的点中找一个值最小的,再在这个点右边找到一个奇偶性和右端点相同的值最小的点,那么我们这次操作就选择这两个点作为一对匹配的括号,不妨设这两个点为 \(x,y\)。
这两个点会将原区间分成三段,每段都是子问题,可以接着做。
用优先队列维护这个过程,每次取出 \(a_x\) 最小的候选区间即可。
代码:https://pastebin.ubuntu.com/p/YGq6RBp78r/。
[ARC079F] Namori Grundy(基环树 + 性质)
首先,图肯定是一个基环外向树。
其次,这个条件等价于每个点的权值是它的出边的点的权值的 \(\text{MEX}\)。
那么对于不在环上的点,我们可以通过叶子节点权值一定是 \(1\) 来从下往上递推得出它的权值。
对于环上的点,我们考虑先把它们的权值设为只考虑非环节点时的权值。
然后对于一条边 \((u,v)\),如果 \(a_u=a_v\),我们肯定需要调整,即将 \(a_u\leftarrow a_u+1\),然后对于 \(u\) 的前驱继续进行调整。
那么可以得出结论:当且仅当环长度是奇数且环上所有点权值相等的时候无解。因为这个时候 \(+1\) 操作会死循环。
代码:https://pastebin.ubuntu.com/p/mC3sXTysHZ/。
11.3
[ARC074F] Lotus Leaves(网络流 + 最小割)
一眼最小割,关键是怎么建图。
建两排点分别表示行和列。
对于每一个 o,将它对应的行和列之间连流量为 \(1\) 的双向边。对于 S,将源点和它对应的行和列连流量为 \(\infty\) 的单向边。对于 T,将它对应的行和列向汇点连流量为 \(\infty\) 的单向边。跑一遍最大流即可。
这是因为最小割的本质是和源点相连的点分成一组,和汇点相连的点分成另一组,其它的点无所谓。两个组之间是不连通的。
这题中,这样建图就保证了从 S 一定不能到 T,因为它们的行列都不能互相到达。
代码:https://pastebin.ubuntu.com/p/VFksYqhyzB/。
[Code+#3] 白金元首与莫斯科(状压 DP)
orz ckf。
先考虑每次暴力去掉每个点怎么做。
设 \(f_{i,s}\) 表示确定了前 \(i\) 行,且第 \(i\) 行往下延伸到第 \(i+1\) 行的状态是 \(s\) 的方案数。
每次更新一行,考虑怎么填横着放的格子。为了不重不漏,我们可以从小到大枚举每个位置,决策这个 \(1\times 2\) 的位置是否放格子。
接下来就要考虑怎么填往下放的格子。容易发现这一定是已经填了的格子的状态的补集的子集。那么先令 \(f_S\leftarrow f_{\complement_U S}\),然后做一遍高维后缀和。
答案即为 \(f_{n,0}\)。
然后考虑怎么同时处理一行的信息。我们肯定是要从前往后和从后往前都要做一遍 DP 然后合并起来。设从前往后做得到的 DP 值记在 \(f\) 数组里,从后往前做得到的 DP 值记在 \(g\) 数组里。
那么对于第 \(i\) 行,我们设 \(f1_*=f_{i-1,*}\),\(f2_*=g_{i+1,*}\)。
然后我们把这一行横着放的格子在 \(f1\) 中决策一遍,那么一个点 \((i,j)\) 的答案就是 \(\sum\limits_{j\not \in S\cup T,S\cap T=\empty}f1_S\times f2_T\)。
对 \(f2\) 做一遍高维前缀和即可快速求出答案。
代码:https://pastebin.ubuntu.com/p/5sSJZ2yPnV/。
[ARC072E] Alice in linear land(性质)
先求出每个前缀操作之后到的位置 \(b_i\)。
那么一次修改操作就相当于可以到达位置 \(0\sim b_{i-1}\),那么当且仅当这些位置在经过 \([i+1,n]\) 这些操作之后都能到达终点才输出 NO。
我们可以把初始距离和经过一些操作后的距离看成一个函数关系,在这个函数上分析性质。
一直在想维护拐点之类的黑科技,但是一直没有突破,不好维护整个函数的性质。
但其实我们并不关心函数具体的样子,我们只关心它最小的不为 \(0\) 的位置(即最长的前缀 \(0\) 的长度)。
设 \(mn_i\) 为操作完 \([i,n]\) 后离终点的距离不为 \(0\) 的最小初始距离。
那么分类讨论一下:
- 若 \(mn_{i+1}\le \frac{a_i}{2}\),那么 \(mn_i=mn_{i+1}\),因为这个时候不能行动。
- 若 \(mn_{i+1}>\frac{a_i}{2}\),那么 \(mn_i=mn_{i+1}+a_i\)。
证明考虑反证法,即如果有一距离 \(x<mn_i\) 在操作之后不能到达终点,那么考虑它是从哪一个位置转移过来,分析过后会产生矛盾。
所以,如果 \(b_{i-1}<mn_{i+1}\) 就输出 NO,否则输出 YES。
代码:https://pastebin.ubuntu.com/p/HtCjQh9MFY/。
[ARC070F] HonestOrUnkind(交互 + 构造)
一道很神奇的题目。
首先,如果 \(a\le b\) 则无解,因为这个时候不友好的人可以都假扮诚实的人,这个时候无法分辨。
剩下的就都是 \(a>b\) 的情况,我们可以类似摩尔投票那样,每次选择两个数,至少消除一个不友好的人。
考虑对于两个人 \(x,y\),如果我们询问 ? x y 返回了 N,那么 \(x\) 和 \(y\) 中至少有一个是不友好的人。
那么我们用一个栈来维护这个过程。具体的,从小到大枚举 \(i\),如果栈为空就直接将 \(i\) 加入栈中;否则每次询问栈顶元素 \(x\):新加的元素 \(i\) 是否友好,如果返回 N 就将 \(x\) 弹出栈,否则就将 \(i\) 压入栈。
这样做完 \(n\) 轮之后,栈顶元素一定是诚实的人,再询问一遍所有的人的情况即可。询问数 \(2n-1\)。
考虑证明这种做法的正确性:
- 因为我们每次都会至少消除一个不友好的人,且每个时刻都保证了诚实的人的数量大于不友好的人的数量,所以最后栈中诚实的人肯定比不友好的人多。
- 如果栈顶是不友好的人,那么它肯定会被前一个诚实的人消除。这是因为栈中始终保证了诚实的人会在不友好的人的前面,否则就可以进行消除操作。
代码很好写:https://pastebin.ubuntu.com/p/njd6RbpGy2/。
11.4
[ARC069F] Flags(二分 + 线段树优化连边 + Tarjan)
最大值最小,明示二分。
二分最大的最小距离 \(x\),那么对于每个点,都要求如果选了它,那么离它距离 \(<x\) 的点都不能选。
我们把所有点拿出来按坐标从小到大排序,记录 \((id,ty)\) 表示它是第 \(id\) 个标志的第 \(ty(=0/1)\) 种选择。
那么就相当于,对于每个点,向它左右距离小于 \(x\) 的所有点连边。同时每个点向它这个标志的另一种选择的点连边,表示这个点不能选择的时候就需要选另一个点。
暴力建图是 \(\mathcal{O}(n^2)\) 的。容易发现我们连边的实际上是一段区间,可以使用线段树优化连边做到 \(\mathcal{O}(n\log n)\)。
判定是否可行的话,直接判断每个标志的两种选择是否在同一个强连通分量里面。
后来发现这有点类似 2-SAT……
代码:https://pastebin.ubuntu.com/p/wgmdZc2brz/。
[洛谷 P8434]「WHOI-2」D&D(高维后缀和 + 双指针 + 前缀和优化 DP)
这个“装饰子集” 的定义其实就是说,一个数在一个集合的“装饰子集”里面,当且仅当这个集合中没有包含它的数。其中包含定义为:如果 \(a\) 包含 \(b\),那么 \(a|b=a\)。
接下来有一个很强的结论:所有合法方案中,每个子串的“装饰子集”一定都等于原串的“装饰子集”。
证明如下:
- 如果有一个数 \(x\) 在原串的“装饰子集”中,且不在所有子串的“装饰子集”中,就说明在每个子串中都有包含 \(x\) 的数。这个时候 \(x\) 就不应该出现在原串的“装饰子集”中,矛盾。
- 如果有一个数 \(y\) 在所有子串的“装饰子集”中,且不在原串的“装饰子集”中,那么在每个子串中都没有包含 \(y\) 的数,因此原串中肯定也没有包含 \(y\) 的数,\(y\) 就应该出现在原串的“装饰子集”中,矛盾。
所以,我们可以先用高维前缀和求出原串的“装饰子集”,再考虑怎么计数。
设 \(f_i\) 表示将前缀 \(1\sim i\) 分成若干段,每段都包含所有原串的“装饰子集”中的数的方案数。
那么设 \(l_i\) 为最大的数满足 \([l_i,i]\) 中存在所有原串中“装饰子集”的数,就会有 \(f_i=\sum\limits_{j=0}^{l_i-1}f_j\)。
\(l_i\) 可以使用双指针快速求出,DP 过程可以前缀和优化。
代码:https://pastebin.ubuntu.com/p/4HhjcKG7Tx/。
[PKUWC2018] 随机算法(状压 DP)
看到 \(n\) 很小,想到状压。
设 \(f_s\) 表示已经当前独立集已经选了所有集合 \(s\) 中的点的概率,\(g_s\) 表示 所有 \(s\) 中的点 和 与这些点有边相连的点 的并集。
那么对于在 \(g_s\) 中的点,它们接下来无论怎么放都不会对答案有影响,因此我们只需要考虑会对当前独立集产生影响的点。不难发现这些点有 \(|U-g_s|\) 个,其中 \(U\) 为全集。
转移有:\(f_s\times\frac{1}{|U-g_s|}\to f_{s\cup\{u\}}(u\not\in g_s)\)。
代码:https://pastebin.ubuntu.com/p/jtB3Dt9gyS/。
[CF1254E] Send Tree to Charlie(并查集 + 组合计数)
一道比较容易有思路的题,但是细节似乎有亿点多……
考虑对于一个 \(a_i\ne 0\),将 \(a_i\to i\) 的路径拿出来,我们就相当于要求:\(a_i\) 到下一个点的边是 \(a_i\) 的所有连边中的被删去的第一条边;\(i\) 到上一个点的边是 \(i\) 的所有连边中被删去的最后一条边;这条路径上相邻两条边在所有和它们的公共点相连的边中的删除顺序也是相邻的(即:对于相邻三个点 \(u\to v\to w\),对于点 \(v\) 来说,\(u-v\) 这条边一定是在 \(v-w\) 这条边的前面一个被删除的),我们将每条边看成点,将其与后面一条边对应的点连边。
那么对于每个点 \(x\) 来说,把所有和它相连的边拿出来,设它们经过上面的处理之后形成了 \(cnt_x\) 条链(如果形成了环肯定无解),\(f1_x\) 表示 \(x\) 是否有已经确定的要删去的第一条边,\(f2_x\) 表示 \(x\) 是否有已经确定的要删去的、和第一条边不同的最后一条边,那么答案就是 \(\prod(cnt_x-f1_x-f2_x)!\)。
判断无解的条件就是:\(\exist i\ne j,a_i=a_j\);连边连出了环(可以用并查集判断);要删去的第一条边和最后一条边属于同一条链,且形成的链数 \(>1\)(并查集 + set 判断)。
代码:https://pastebin.ubuntu.com/p/9ydf8nQTRk/。
11.5
[ARC078F] Mole and Abandoned Mine(状压 DP)
考虑图最终的形态,肯定是随便拿一条 \(1\to n\) 的路径,把路径上的边全部去掉,会形成若干个连通块,每个连通块的根分别对应路径上的一个点。
发现数据范围很小,考虑状压 DP。
设 \(f_{s,i}\) 表示已经选完了集合 \(s\) 中的所有点,当前 \(1\to i\) 的路径只有一条,能保留的边权的最大和是多少。
转移枚举下一个点 \(j\),或者枚举 \(i\) 所在连通块内的点,这个点集内部的所有边都可以保留(这个可以预处理)。
代码:https://pastebin.ubuntu.com/p/mCXgSwpZ96/。
[ARC078E] Awkward Response(交互 + 二分)
不是很难的交互。
先询问一遍 1000000000 判断掉所有形如 10...0 的情况。之后可以问若干个 9 确定 0 的个数。
接下来多次询问 10...0 来求出位数。对于每一位直接二分。
注意一些实现细节。
代码:https://pastebin.ubuntu.com/p/Mds4YXgd5D/。
2022.11.5 考试 T1 T2
11.6
[ARC077E] guruguru(二阶差分)
考虑每一轮移动时,理想亮度在每个位置的时候所需要的最小步数。
设起点为 \(l\),终点为 \(r\),分类讨论:
- \(l=r\),无论理想亮度是什么贡献都是 \(0\)。
- \(l<r\),\(1\sim l\) 和 \(r+1\sim n\) 的贡献都是 \(r-l\),\(l+1\sim r\) 的贡献是首项为 \(r-l\)、公差为 \(-1\) 的等差数列。特判 \(l=1\) 的情况。
- \(l>r\),\(1\sim r\) 的贡献是首项为 \(r\)、公差为 \(-1\) 的等差数列,\(r+1\sim l+1\) 的贡献都是 \(n-l+r\),\(l+2\sim n\) 的贡献是首项为 \(n-l+r-1\)、公差为 \(-1\) 的等差数列。特判 \(r=1\) 的情况。
对于每种情况,都可以用二阶差分表示出来。然后做两遍前缀和就行了。
代码:https://pastebin.ubuntu.com/p/gxmsGMXxkW/。
11.7
[CF1750E] Bracket Cost(分治 + 排序)
对于一个区间来说,设 \(u\) 表示失配的左括号数量,\(v\) 表示失配的右括号数量,那么操作代价就是 \(\max(u,v)\)。
证明:删掉所有匹配的括号之后,失配的括号一定形如 )))))((((((,进行 \(\min(u,v)\) 次右移操作,再补上 \(|u-v|\) 个括号就行了。
考虑分治,假设当前分治到的区间是 \([l,r]\),考虑如何计算所有跨过中点 \(mid\) 的区间的代价之和。
设 \(L_{x}\)、\(R_x\) 分别表示 \([x,mid]\) 这一段区间做括号匹配后失配的左括号和右括号数量,\(L_y\)、\(R_y\) 分别表示 \([mid+1,y]\) 这一段区间做括号匹配后失配的左括号和右括号数量。
那么对于区间 \([x,y]\) 来说:
- 其失配的左括号数量为 \(L_x+L_y-\min(L_x,R_y)\),失配的右括号数量为 \(R_x+R_y-\min(L_x,R_y)\)。
- 分别化简一下式子:
- \(L_x+L_y-\min(L_x,R_y)=L_x+L_y+\max(-L_x,-R_y)=L_y+\max(0,L_x-R_y)=L_y-R_y+\max(R_y,L_x)\)。
- \(R_x+R_y-\min(L_x,R_y)=R_x+R_y+\max(-L_x,-R_y)=R_x+\max(R_y-L_x,0)=R_x-L_x+\max(R_y,L_x)\)。
- 所以区间 \([x,y]\) 的代价就是 \(\max(R_y,L_x)+\max(L_y-R_y,R_x-L_x)\)。
对于前后两项分开计算。排序后计算出另一边值比它小的数的数量,乘上贡献即可。
代码:https://pastebin.ubuntu.com/p/QMGkGvjspY/。
[ARC081E] Don't Be a Subsequence(DP)
有一个 弱化版,那个题只要求最小长度,所以可以考虑将序列分成若干段,每段都是出现了所有 \(k\) 种数字的极小连续段(最后一段可以不选全),那么答案就是段数 +1。
这个题中,因为字符集不大,所以可以考虑使用 DP 输出方案。
设 \(f_i\) 表示 \([i,n]\) 这个后缀的最小答案。记 \(nxt_{i,j}\) 表示位置 \(i\) 后面第一个字母 \(j\) 的出现位置,那么转移就有:\(f_i=\min(f_{nxt_{i,j}+1}+1)\)。
输出方案只需要从第一个位置开始,往后依次找能转移过来的最小字母即可。
代码:https://pastebin.ubuntu.com/p/2PtPtk6pb5/。
[CF1710D] Recover the Tree(构造)
对于一个好的区间 \(A=[l,r]\),我们考虑它的所有好的子区间 \(A_1,A_2,\dots,A_k\)。
我们用递归去做它。只要每次 \(\mathcal{O}(n)\),那么总的复杂度也是对的。
注意到如果两个区间 \(i\ne j\) 满足 \(A_i\) 包含 \(A_j\),那么我们在这一层中就不需要考虑 \(A_j\),只需要做完 \(A\) 之后递归到 \(A_i\) 下去即可。
那么这一层中处理的 \(A_1,A_2,\dots,A_k\) 就都是互不包含的。不妨设 \([l,r]\) 中的连续段情况是 \([l,x_1],[x_1+1,x_2],\dots,[x_k+1,r]\)。
我们考虑使用并查集维护连续段。
那么如果 \(k=1\),可以直接连接 \((l,r)\);\(k=2\) 无解(因为我们要连成一棵树,并且必须要连接 \((l,r)\),那么中间的那一个好区间不管往哪边连都会导致有不好的区间变成好区间,不符合条件);\(k>2\) 考虑连接 \((l,r)\)、\((l,x_k)\) 以及 \((r,x_{2\sim k-1})\)。
代码:https://pastebin.ubuntu.com/p/HS8v3XGd2X/。
[ABC276G] Count Sequences(组合计数 + 差分)
题意:
求满足下列条件的长度为 \(n\) 的序列 \(a\) 的个数,对 \(998244353\) 取模:
- \(0\le a_1\le a_2\le \dots\le a_n\le m\)。
- \(\forall 2\le i\le n,a_i\bmod 3\ne a_{i-1}\bmod 3\)。
数据范围:\(2\le n\le 10^7,1\le m\le 10^7\)。
看到递增序列,马上想到差分。设原序列的差分数组为 \(b\),那么 \(b\) 数组需要满足:
- \(\forall 1\le i\le n,b_i\ge 0\)。
- \(\forall 2\le i\le n,b_i\bmod 3=1/2\)。
那么我们可以把 \(b_i(i>1)\) 写成 \(3\times k_i+1/2\) 的形式,把 \(b_1\) 写成 \(3\times k_1+0/1/2\) 的形式。
记 \(s_i=\sum(b_i\bmod 3)\)。
枚举 \(b_1\bmod 3\) 和 \(b_i\bmod 3=2(i>1)\) 的数量,不妨设为 \(i\) 和 \(j\),那么就有 \(s_{i+(n-1)+j}\leftarrow \binom{n-1}{j}\)。
我们考虑枚举 \(S=\sum k_i\),那么分配 \(k_i\) 的方案数就是 \(\binom{S+n-1}{n-1}\)(插板法),剩下的方案数就是 \(\sum\limits_{i=0}^{M-3S}s_i\),可以用前缀和快速求出,二者相乘就是当前方案数。再求个和就是答案。
代码:https://pastebin.ubuntu.com/p/wV4J4GxMq3/。
11.8
2022.11.8 考试 T1 T2 T4
11.9
2022.11.8 考试 T3
[CF1696F] Tree Recovery(构造)
注意到如果钦定了一条边 \((x,y)\) 在最终的树边集合中,那么每次如果存在另一个点 \(z\) 满足 \(dis_{x,z}=dis_{x,y}\),那么一定存在边 \((x,z)\)。
枚举哪一条边在最终树边集合中,每次 BFS 扩展边。时间复杂度 \(\mathcal{O}(n^4)\)。
代码:https://pastebin.ubuntu.com/p/xq9bQ6KQ5D/。
11.10
ICPC World Finals 2021 H
2022.11.10 考试 T2
11.11
2022.10.15 考试 T4
[CF1747E] List Generation(组合计数)
考虑把这两个数组映射到平面上来,即第 \(i\) 位确定的是一个点 \((a_i,b_i)\),那么题目要求的就是从 \((0,0)\) 走到 \((n,m)\),每次必须横坐标增加或者纵坐标增加或者横纵坐标都增加,问所有方案的点数之和。
我们要求,每种合法的选择数组的方案都可以对应到唯一一条路径。
考虑钦定一些点一定在路径上,比较自然地想到选择“拐点”,即向右和向上的交界处的所有点,那么这些点就唯一确定了一条路径。设选择了 \(i\) 个“拐点”,路径上其它的点(即除了“拐点”、起点和终点的点)数量为 \(s=n+m-i-1\),那么这 \(s\) 个点是可选可不选,所以这条路径的贡献为 \(\sum\limits_{i=0}^{\min(n,m)}\binom{n}{i}\binom{m}{i}\sum\limits_{j=0}^s\binom{s}{j}(i+2+j)\)。
考虑化简后面的那一块:
那么这就可以线性直接算了。
代码:https://pastebin.ubuntu.com/p/SJMFZPyprh/。
[ARC080F] Prime Flip(差分 + 网络流 + 贪心)
将区间操作转化成差分,这一步很容易想到。
设 \(a_i\) 表示每个位置上要被操作的次数的奇偶性,\(b_i\) 表示 \(a\) 序列的差分。即如果 \(a_i\ne a_{i+1}\) 那么 \(b_i=1\),否则 \(b_i=0\)。
那么对于一个极长的正面朝上的硬币的连续段 \([l,r]\),\(b_{l-1}=b_r=1\)。
接下来我们只将 \(b_i=1\) 的位置拿出来考虑,因为只有可能在这些位置上操作。
考虑每次操作实际上就是消掉两个 \(1\),原因:
- 如果操作的区间为 \([l,r]\),那么连边 \((l-1,r)\)。
- 最终,只有 \(b_i=1\) 的点度数为奇数,其他点的度数为偶数。
- 那么每次可以看成选择两个 \(b_i=1\) 的位置,拿出这两个点之间的一条链,并且删去这条链。这样只会改变这两个点的度数奇偶性。最后剩下的点度数都是偶数。
- 所以我们把操作看成每次消掉两个 \(1\) 一定是不劣的。
每次选择两个位置 \([l,r]\),相当于翻转区间 \([l+1,r]\),代价需要根据长度分类讨论:
- 如果长度为奇质数,就只需要一次操作。
- 如果长度为偶数,就需要两次(哥德巴赫猜想)。
- 如果长度为奇合数,那么需要三次操作(\(len=1\to 1=3+5-7\),\(len>3\) 的时候先减去 \(3\) 再转化成偶数的情况)。
可以证明最优策略下第三种操作最多使用 \(1\) 次。
那么我们可以将两个距离为奇质数的点之间连边,跑一遍最大匹配,剩下的点尽量使用第二种操作,最后使用第三种操作。
代码:https://pastebin.ubuntu.com/p/Q3MJgFsXG3/。
[CF1720E] Misha and Paintings(结论 + 二维前缀和)
因为这种操作不太好用 DP 求最小值,所以可以考虑猜结论。
首先,如果原矩阵中的颜色数 \(\le k\),那么答案显然就是 \(k-\) 颜色数。因为一次操作最多只能增加一种颜色。
结论:
当原矩阵中的颜色数 \(>k\) 的时候,答案 \(\le 2\)。
证明:
我们只需要构造出一种只需要两次操作的方案,就可以证明这个结论。
先以 \((1,1)\) 为左上角操作一次,正方形大小为满足正方形内颜色数 \(\le k\) 的最大正方形,颜色为之前已经在矩阵中的某种颜色。
然后以 第一次操作的正方形的右下角的右下角 为 第二次操作的正方形的右下角,向左上扩展,颜色和第一次操作的一样。容易发现每次扩展都只会最多减少两种颜色。所以当颜色数减到 \(k/k-1\) 的时候就停止扩展。如果最后颜色数是 \(k-1\),那么就将这两次操作的颜色设为某种新的颜色即可。
具体可见洛谷题解区的图。
那么接下来我们只需要考虑什么时候答案为 \(1\)。
假设我们已经选择了操作的正方形,则答案为 \(1\) 当且仅当这个正方形外面的颜色种数为 \(k/k-1\)。
所以我们将问题转化为:统计完全包含于某个矩形内的颜色数量。
首先肯定要算出完全包含某种颜色的矩形的四个边界。
直观的想法是枚举左上角,然后枚举边长,每次递推,但是这是 \(\mathcal{O}(n^4)\) 的。
那么可以转换枚举量:先枚举边长,然后对于每种颜色,用二维前缀和计算出当矩形的左上角在哪里的时候会完全包含这种颜色,最后再枚举左上角统计。
代码:https://pastebin.ubuntu.com/p/zhKSC5W8sd/。
11.12
[ARC081F] Flip and Rectangles(结论 + 悬线法 DP)
首先,答案至少是 \(\max(n,m)\),因为我们总可以通过一些操作使得某一行 / 一列变成全黑。
先考虑暴力怎么做:枚举矩形的第一列,然后向右扩展,一个矩形满足条件当且仅当每一列都要么和第一列相同,要么和第一列相反。
还是不会做,怎么办?考虑将一个大的问题拆分成小问题,并且从小问题中寻找充要条件。
本题中,如果我们观察合法子矩形的相邻两列,那么每一个 \(2\times 2\) 的子矩形都有 偶数个黑格子。
容易发现这就是原条件的一个充要条件。
那么只需要设 \(a_{i,j}\) 表示以 \((i,j)\) 为左上角的一个 \(2\times 2\) 的子矩阵中是否有偶数个黑格子,再求一边最大全 \(1\) 子矩形就解决了原问题。这个可以用 悬线法 方便地解决。
代码:https://pastebin.ubuntu.com/p/7dNj9vT4NB/。
[ARC102E] Stop. Otherwise...(组合计数)
考虑这个条件实际上限制了什么:假设现在我们要求 \(x\) 的答案,如果两个整数 \(i,j\in[1,k],i\ne j\) 且 \(i+j=x\),那么一种合法方案中 \(i\) 和 \(j\) 最多只会有一个出现了。特别的,当 \(i=j\) 的时候,\(i\) 最多有一个。
先考虑 \(x\) 是奇数的情况。把这样的两个数看成一对数对,枚举有多少对在方案中出现过(数量 \(>0\)),那么在这些选择的数对中,每一个都有 \(2\) 的贡献(两个任选一个),剩下不在任何一个数对中的数随便选(数量 \(\ge 0\))。形式化地,设有 \(nc\) 对数对,有 \(uc\) 个数没有出现,则答案为:
再考虑 \(x\) 是偶数的情况。将 \(uc\leftarrow uc-1\),容易发现我们只需要特殊处理 \(i=j\) 的情况。不选择它就直接求一遍上面的式子,选择就将第二个组合数的上指标再 \(-1\),代表剩下的个数总和为 \(n-1\)。
代码:https://pastebin.ubuntu.com/p/D9XH3SbTBR/。
2022.11.12 考试 T1 T2 T3
11.14
2022.11.14 考试 T1 T2
[清华集训2016] 你的生命已如风中残烛(Raney 引理 + 组合计数)
Raney 引理:
如果一个序列 \(x_{1\dots n}\) 满足 \(\sum\limits_{i=1}^n x_i=1\),那么这个序列有且仅有一个循环移位满足其所有前缀和都是正数。
证明考虑转化到平面上的折线,具体此处略去。
那么如果要计数,有一种更加方便的理解:
对于一个环 \(x_{1\dots n}\) 满足 \(\sum\limits_{i=1}^n x_i=1\),那么这个环有且仅有一种断环成链的方式满足这条链的所有前缀和都是正数。
那么对于 \(n\) 个数 \(x_{1\dots n}\),满足所有前缀和都是正数的排列的方案数就是圆排列数即 \((n-1)!\)。
本题中,我们可以将前 \(m\) 项拿出来,将每个数 \(-1\),那么序列的和就变成了 \(0\),问题的条件也就转化为要求每个前缀和都 \(\ge 0\)。
但这样还是不太好做,考虑怎么样才能使每个环的贡献都为 \(1\)。在序列的末尾加上一个 \(-1\) 可以完美地解决这个问题。怎么理解?考虑将所有数取反后逆序,要求后缀和都 \(>0\),此时 \(1\)(即 \(-1\) 取反)是序列中最大的数,它在合法方案中肯定会排在第一个,所以这个时候就可以算圆排列了,结果为 \((m+1-1)!=m!\)。
接下来考虑 \(-1\) 之间的顺序,原来有 \(m-n\) 个 \(-1\),方案为 \((m-n)!\),现在为 \((m-n+1)!\),多乘了一个 \(m-n+1\),所以最终答案为 \(\frac{m!}{m-n+1}\)。
代码:https://pastebin.ubuntu.com/p/WKv7tj93sQ/。
[ARC100E] Or Plus Max(高维前缀和)
我们令两个数 \(i\)、\(j\) 如果满足 \(i\) 的每一个二进制位都不比 \(j\) 的这一位大,那么称 \(i\subseteq j\)。
则我们可以将所求转化为:设 \(ans_k=\max\limits_{i,j\subseteq k}\{a_i+a_j\}\),所求即为 \(\max\limits_{x=1}^k\{ans_x\}\)。
前者可以使用高维前缀和记录最大值和次大值求出。
代码:https://pastebin.ubuntu.com/p/pJVBvfYGZm/。
[ARC101F] Robots and Exits(性质 + 二维偏序)
注意到我们只关心每个机器人从哪一个出口出去,并且每个机器人只有可能从它相邻的左 / 右出口出去。
如果我们把操作序列写出来,不难发现其实每个位置具体的值影响并不大,只有当前缀向左 / 右走的最大距离被更新的时候才有可能影响答案。
考虑建立一个坐标系,横轴为向左走的最大距离,纵轴为向右走的最大距离。那么每一种操作序列就都可以对应一条折线。设 \(x_i\) 表示点 \(i\) 到前一个出口的距离,\(y_i\) 表示点 \(i\) 到后一个出口的距离,则如果这条折线先与直线 \(x=x_i\) 相交,就说明点 \(i\) 是从前一个出口出去的;如果它先与直线 \(y=y_i\) 相交,就说明点 \(i\) 是从后一个出口出去的。我们把它抽象成一个点 \((x_i-0.5,y_i-0.5)\),如果这个点在折线的下方就是从后一个出口出去,否则就是从前一个出口出去。
所以,我们就需要对于每一个集合 \(\{1,2,\dots,n\}\) 的子集 \(S\),找到一条唯一对应的折线,使得集合 \(S\) 内的点都在折线下方,且不在集合 \(S\) 内的点都在折线上方,表示集合 \(S\) 内的点从后一个出口出去对应的操作序列。如果找不到就说明该情况不可行。容易发现如果一直卡着上界构造在有解的情况下是可以找到解的。
那么就考虑 DP,设 \(f_{i}\) 表示第 \(i\) 个点是当前折线上界对应的点(即最后一个被考虑到从后一个出口出去的点),那么转移有 \(f_i=1+\sum\limits_{a_j<a_i,b_j<b_i}f_j\)。不难发现这是一个二维偏序,树状数组维护即可。
代码:https://paste.ubuntu.com/p/cBzjhYS7yd/。
11.15
2022.11.15 考试 T1 T2 T3
[ARC102F] Revenge of BBuBBBlesort!(性质 + 找充要条件)
铜牌🥉题/tuu
首先,每个数所在位置的奇偶性肯定不会改变,所以如果 \(p_i\bmod 2\ne i\bmod 2\) 就肯定无解。
结论:
如果我们在位置 \(i\) 进行了操作,那么肯定不会在位置 \(i-1\) 或 \(i+1\) 进行操作。
证明:
在第 \(i\) 个位置操作之后,肯定会有 \(p_{i-1}<p_i<p_{i+1}\)。
接下来,如果我们下一个在相邻位置上操作的是 \(i-1\),那么一定有 \(p_{i-1}>p_i\),就需要使得 \(p_{i-1}\) 变大或者使得 \(p_i\) 变小,前者需要操作位置 \(i-2\),后者需要操作 \(i+1\)(不符合假设)。而这样一直推下去,直到需要操作位置 \(1\) 时无法进行,矛盾。
由这个结论可以得出以下 推论:
只有可能在 \(p_i=i\) 的位置上进行操作。
原因很简单,如果 \(p_i\ne i\),我们就必须要操作位置 \(i-1/i+1\),但我们还需要操作位置 \(i\),不符合上述结论。
设 \(b_i=[p_i=i]\),那么如果有 \(b_i=b_{i-1}\),则位置 \(i\) 和 \(i-1\) 都不会被操作。原因显然。
再设 \(c_i\) 表示位置 \(i\) 有没有可能被操作,可以发现整个序列可以被分成若干个 \(c_i\) \(0/1\) 交替的连续段,每个连续段可以分开单独考虑。因为段与段之间显然不会发生数的交换,因为端点处都不会进行操作。
对于每个连续段,合法当且仅当:
- 设这个连续段为 \([l,r]\),那么 \(\forall l\le i\le r,l\le p_i\le r\)。
- 把所有不会进行操作的位置拿出来考虑,不存在长度 \(\ge 3\) 的下降子序列。
第一个证明很显然。第二个证明可以考虑手玩。此处略去。
有解当且仅当每一个分出来每一个连续段都合法。
代码:https://pastebin.ubuntu.com/p/pyD3DFy9VT/。
[ARC107F] Sum of Abs(最优化 + 时间倒流 + 二分图最大权独立集 + 网络流最小割)
把删点反过来变成加点,即一开始把所有点都看成被删去(收益为 \(-\sum a_i\)),然后再考虑加入一些点更新收益。
注意到一个连通块中的贡献要么都是 \(b_i\),要么都是 \(-b_i\),这启发我们从分配的角度考虑问题。
一个点有三种状态:被删去、\(b_i\) 选正贡献 和 \(b_i\) 选负贡献。增加的收益分别为 \(0\)、\(a_i+b_i\)、\(a_i-b_i\)。如果两个点有边相连,并且它们都没有被删去,那么它们 \(b_i\) 的贡献的系数是一样的。
考虑建一张二分图,每个点拆成两个点,分别是正点和负点,权值分别为 \(a_i+b_i\) 和 \(a_i-b_i\)。在同一个点的正点和负点之间连一条边,如果有边 \((u,v)\) 就将 \(u\) 的正点和 \(v\) 的负点连边、\(v\) 的正点和 \(u\) 的负点连边。这个二分图的最大权独立集就是最大收益。
这个可以跑网络流来实现。具体的,\(S\) 向所有正点连边,流量为 \(a_i+b_i\);所有负点向 \(T\) 连边,流量为 \(a_i-b_i\);正点和负点之间的边流量均为 \(+\infty\),\(\sum\) 点权之和 减去最小割就是最大权独立集。值得注意的是,如果流量为负数,那么这条边肯定在最小割中,可以直接加入答案。
代码:https://pastebin.ubuntu.com/p/dCJ3pbdnmD/。
[ARC107E] Mex Mat(打表找规律)
打表发现 \((4,4)\) 到 \((n,n)\) 的矩阵每一条主对角线上的数都是一样的。
那么只要预处理出矩阵的前 \(4\) 行 & 列就行了。
代码:https://pastebin.ubuntu.com/p/fDk5pyMHzr/。
11.16
2022.11.14 考试 T4 60 分(反悔贪心)
题意:最大 \(m\) 段子段和。
有一种 \(\mathcal{O}(n\log n)\) 的反悔贪心做法。
首先把符号相同的数缩成一段,并且如果首尾是负数肯定不优,可以删去。
那么我们就将序列变成了 + - + - ... - + 的样子。
先选所有正数,如果个数 \(\le m\) 就可以直接输出。
否则,我们需要合并一些段或者丢弃一些段来满足条件。
将 + - + 的段合并的代价是中间那个数的值;将 - + - 的段合并的代价是中间那个数的值的相反数。
不难发现每次选择代价最小的段合并是最优的。
可以使用优先队列 + 链表来维护这一过程。
代码:https://pastebin.ubuntu.com/p/H26z4JyNHY/。
[ARC106E] Medals(二分 + 二分图 + Hall 定理)
遇到这种“一天只能匹配一个人”的问题,需要想到二分图匹配!!1
考虑二分答案 \(mid\)。有一种显然的建图方式:\(S\) 向每个人连流量为 \(k\) 的边,如果某个人在某一天去玩,就从这个人向这一天连流量为 \(1\) 的边,最后每一天向 \(T\) 连流量为 \(1\) 的边。判断是否满流即可。
如何快速判断是否满流?可以考虑一种类似 Hall 定理的理解:如果对于每个左部点集合 \(S\) 都满足 \(k|S|\le |\mathrm{N}(S)|\),那么就满流。考虑把并集转化成交集的补集,求出 \(f_s\) 表示集合 \(s\) 内的人有多少天都没有去玩,这个可以使用高维后缀和方便求出。那么 \(mid-f_s\) 就是 \(|\mathrm{N}(S)|\),从而可以快速判断。
代码:https://pastebin.ubuntu.com/p/C8ky4mJkN7/。
[ARC105E] Keep Graph Disconnected(博弈论)
题面里所说的操作是指加一条无向边且保证每个时刻图都没有重边自环。
最终状态肯定是有两个连通块,每个连通块都是完全图,且 \(1\) 和 \(n\) 分别属于这两个连通块。
首先,有以下几种连边方法:和 \(1\) 所在的连通块合并;和 \(n\) 所在的连通块合并;将任意两个连通块合并;连通块内部连边。
容易发现最后一种连边对最终状态没有影响,是无效连边。
假设最终 \(1\) 所在的连通块点数为 \(x\),那么图的边数就是 \(m'=\binom{n}{2}-x(n-x)\),显然当 \(n\) 为奇数的时候不管 \(x\) 的值是什么 \(m'\) 的奇偶性都不变,所以可以直接判断 \(m'-m\) 的奇偶性确定胜负。
接下来设原图中 \(1\) 所在连通块大小的奇偶性为 \(a\in\{0,1\}\),\(n\) 所在的连通块大小的奇偶性为 \(b\)。
先手希望 \(m'-m\) 变成奇数,也就是说想要让 \(x\) 的奇偶性和 \(\binom{n}{2}-m\) 不同,那么设先手所希望的 \(x\) 的奇偶性为 \(A\)。
因为我们只关心 \(x\) 的奇偶性,并且只有合并大小为奇数的连通块才会改变奇偶性,所以我们只需要讨论大小为奇数的连通块的个数的奇偶性。
接下来分类讨论:
- 如果 \(a\ne b\),剩下的肯定有奇数个大小为奇数的连通块(因为 \(n\) 为偶数)。如果 \(a=A\),先手可以先合并一个到 \(n\) 所在的连通块,接下来模仿后手操作,肯定能保持 \(a=A\);如果 \(a\ne A\),先手可以先合并一个到 \(1\) 所在的连通块,然后继续模仿操作。因此,先手必胜。
- 如果 \(a=b\),剩下的肯定有偶数个大小为奇数的连通块。此时如果 \(a=A\),那么先手只需要第一次合并两个大小为奇数的连通块,第二次后手如果也这样做最后结果不会变,否则如果后手合并到 \(1/n\) 所在的连通块就变成了上一种情况,先手必胜;如果 \(a\ne A\),无论先手怎么操作后手都可以使得 \(a=b\),并且保持 \(a\ne A\),先手必败。
代码:https://pastebin.ubuntu.com/p/F8Zfg7pKyV/。
[ARC105D] Let's Play Nim(博弈论)
发现当我们确定了 \(n\) 的奇偶性的也就确定了后面 Nim 游戏的先后手。
当 \(n\) 是奇数的时候,Nim 游戏中一开始的后手是先手,所以先手想要尽量保持异或和为 \(0\)。如果最后异或和为 \(0\),那么每一个二进制位上都有偶数个数,后手只需要进行一次进位就肯定可以使得最终异或和不为 \(0\)。所以后手必胜。
当 \(n\) 是偶数的时候,如果所有数的出现次数都是偶数,那么后手可以模仿操作使得最终异或和为 \(0\),后手必胜;否则先手在加某个数的时候后手无法对称,最终异或和不为 \(0\),先手必胜。
代码:https://pastebin.ubuntu.com/p/9975XWMw3j/。
[ARC104E] Random LIS(暴力 + 容斥)
发现 \(n\) 很小,而且所求只和数之间的大小关系有关,因此考虑 \(n!\) 暴力枚举数之间的大小关系。即枚举一个排列 \(p\),要求 \(a_{p_1}(<\operatorname{or} \le)a_{p_2}(<\operatorname{or} \le)\dots(<\operatorname{or} \le)a_{p_n}\)。其中如果 \(p_i<p_{i+1}\) 那么取到小于号,否则取小于等于。这是因为我们要保证排列和一个限制条件一一对应,那么相同的数我们钦定大的放在前面,这样也更加方便计算 LIS。可以直接 \(\mathcal{O}(n^2)\) 暴力求出 LIS。
考虑怎么把 \(<\) 变成 \(\le\),只需要将所有 \([i+1,n]\) 的限制全部减少 \(1\)(相当于给后面的都垫上一个 \(1\))。然后将限制取一个后缀 \(\min\),原因显然。如果 \(a_{p_1}\) 的限制 \(<1\) 就肯定没有满足限制的方案。设每个数的上界组成的数组是 \(\{b_i\}\)
经典套路:对于一个非递减且每个位置有上界的序列计数,考虑转化到格路上来,看成从 \((1,1)\) 走到 \((n+1,+\infty)\),且每个横坐标对应的纵坐标的值不能超过上界。
然后容斥,设 \(f_i\) 表示考虑前 \(i\) 个上界(走到 \((i,b_i)\))合法的序列数量。转移考虑枚举第一个不合法的位置(相当于挪到上界 \(+1\) 的位置),即 \(f_i=\binom{b_i-1+i-1}{i-1}-\sum\limits_{j<i}f_j\times\binom{b_i-b_j-1+i-j}{i-j}\)。
代码:https://pastebin.ubuntu.com/p/gQ3TzDxX8Y/。
11.17
[ARC106F] Figures(Prufer 序列 + 组合计数)
如果已知每个点的度数 \(deg_i\),那么方案数为 \(\prod\limits_{i=1}^n a_i^{\underline{deg_i}}\)。
对于树的计数,考虑 Prufer 序列。
如果一个点在 Prufer 序列中出现了 \(x\) 次,那么它在原树中的度数为 \(x+1\)。
设每个点在 Prufer 序列中出现了 \(d_i\) 次,则答案为 \(\prod\limits_{i=1}^n a_i^{\underline{d_i+1}}=(\prod\limits_{i=1}^n a_i)\times (\prod\limits_{i=1}^n (a_i-1)^{\underline{d_i}})\)。
对于后者的计算,可以考虑组合意义。即有 \(n\) 种物品,每种物品有 \(a_i-1\) 个,每个物品相互区分,要从每种物品中选择若干个,一共选择 \(\sum d_i=n-2\) 个,问选择的方案数。实际上相当于在所有物品中选择 \(n-2\) 个,方案数为 \((\sum\limits_{i=1}^n(a_i-1))^{\underline{n-2}}\)。
这道题启发我们,不太好入手的题目考虑先弱化条件,思考解题方法,然后再把去掉的限制条件加入,看是否能够扩展原来的解法。
代码:https://pastebin.ubuntu.com/p/yKjRWyHjHb/。
2022.11.17 考试 T1 T2 T3
11.18
[CF1566G] Four Vertices(性质 + set)
首先,因为边权都是正的,所以肯定选的边数尽量少更优。
直观想法是直接选择两条边,但还有一种情况没有被考虑到:即有一个度数 \(\ge 3\) 的时候,可以选择这个点以及和它相连的边权最小的 \(3\) 个点,同样可以有一种选择方案。
考虑怎么维护这两种情况。后者比较简单,对于每个点用一个 set 维护和它相连的边,如果度数 \(\ge 3\) 就将前 \(3\) 小的边的权值和加入一个 multiset 中,动态维护是方便的。
对于前者,首先需要一个引理:如果只保留每个点及其相连的边权最小的 \(3\) 条边,那么这张新图上的答案和原图的答案是等价的。证明考虑反证法,可以利用抽屉原理导出矛盾。注意只有当一条边的权值在它的两端点相连的边中都是前 \(3\) 小的才可以被保留,不然不优。
询问的时候,讨论最小的边有没有被选:如果被选了,那么只需要考虑全局前 \(6\) 小的边就可以了;如果没被选,那么只考虑前 \(5\) 小的边也能得到最优解。证明同样考虑抽屉原理。
所以可以暴力拿出全局前 \(6\) 小的边,两两枚举是否可能作为答案即可。
代码:https://pastebin.ubuntu.com/p/WrX34fcqcT/。
[ABC108D / ARC108B] All Your Paths are Different Lengths(构造)
不会构造,我该怎么办???
考虑二进制拆分构造。
当 \(L=2^k\) 的时候,可以建 \(k\) 个点,第 \(i\) 个点向第 \(i+1\) 个点连两条边,边权分别为 \(0\) 和 \(2^{i-1}\)。
考虑更一般的情况,设 \(k\) 为 \(L\) 的二进制最高位,那么还是和上面的一样建图,这样处理了边权为 \([0,2^k-1]\) 的情况。
进一步地,考虑卡着 \(L\) 后几位的下界构造。如果 \(L\) 的第 \(i\) 位为 \(1\) ,就从第 \(i\) 个点向第 \(n\) 个点连边,边权为 \(L\) 抹去这一位后面的所有位的值。
代码:https://pastebin.ubuntu.com/p/jF7d5rvNqr/。
[ARC104F] Visibility Sequence(DP + 图论建模)
这种 \(p_i=\max\limits_{h_j>h_i}{j}\) 建树的套路很常见,要多积累!
在序列的最前面放上一个 \(+\infty\),这样就保证所有的 \(p_i\ge 0\)。
然后由每一个 \(i\) 向 \(p_i\) 连一条边,这样会连出一棵树。
关键性质:因为这是一个栈,所以每一个 \([p_i,i]\) 的区间要么包含,要么不交。
所以一个子树内的编号是一段区间!
进一步观察,如果一个 \(p\) 序列合法,那么一个值 \(h_i\) 需要保证它的值比所有儿子的值大、且不小于它左边第一个兄弟节点的值。设 \(c_i\) 为根据这个条件求出的 \(h_i\) 的下界,那么一个 \(\{c_i\}\) 唯一对应一个合法的 \(p\) 序列,并且所有合法的 \(\{h_i\}\) 都需要满足 \(\forall 1\le i\le n,h_i\ge c_i\)。
所以我们只需要对合法的 \(\{c_i\}\) 计数就可以求得原问题的答案了!
因为子树编号是一段连续区间,所以考虑区间 DP。设 \(f_{i,j,k}\) 表示 \([i-1,j]\) 形成了一棵子树,\(i-1\) 最靠右的儿子的上界 \(\le k\),\([l,r]\) 形成的 \(p\) 序列的方案数。转移枚举最右边的子树的编号区间 \([l,j]\),值域上界为 \(\min(k,a_l)\)。
代码:https://pastebin.ubuntu.com/p/qB6wxcC9Bt/。
[ARC114F] Permutation Division / 2022.11.10 考试 T1 诺贝尔化学奖(chemistry)
下面使用 \(m\) 指代题面中的 \(k\)。
我们先进行一些观察。
有一些比较显然的性质:
- 分段重排之后的序列字典序肯定不会比原来的序列小。
- 划分的段数越多,重排之后得到的序列字典序越大。
如果 \(p_1\le m\),那么肯定选择 \(1\sim m\) 作为段首,因为如果不这样做,第一个数的值肯定会 \(>m\)。
接下来只需要考虑 \(p_1>m\) 的情况。
有一个最小化字典序的常用 trick 是 fix 一段前缀,然后使后面一位尽量小。对应到这道题中就是找到一个最长的 LCP,然后使后面一位尽量小。
首先考虑怎么找最长的 LCP。
不难发现在 LCP 里面的段的开头一定是递减的,不然可以交换某两个使得字典序变大。并且 LCP 后面的一位肯定是另一段的开头,也就是说 LCP 里面的段都是完整的。
可以预处理出 \(dp_i\) 表示考虑前 \(i\) 个数,包含 \(p_1\) 和 \(p_i\) 的最长下降子序列最长是多少;同时记录 \(pre_i\) 表示 \(i\) 在最长下降子序列中的前驱。转移可以使用线段树优化。
二分最长的 LCP 长度 \(x\),枚举 LCP 里面最后一段的开头 \(i\),那么 LCP 之后至少需要划分出 \(m-dp_i\) 段,即至少需要有 \(m-dp_i\) 个 \(<p_i\) 的数。如果不存在这样的 \(i\) 说明不可能存在长度为 \(x\) 的 LCP。
接下来考虑在知道 LCP 之后,怎么最小化后面的字典序。
由性质 2 可知,我们需要分到尽量少的段。找到一个最大化 \(m-dp_i\) 的位置 \(pos\),那么只需要在 LCP 后面找到前 \(m-dp_{pos}\) 小的数,将它们作为段首。这样可以使得字典序最小。
时间复杂度 \(\mathcal{O}(n\log n)\)。
代码:https://pastebin.ubuntu.com/p/HrrktCC59t/。
11.19
2022.11.19 考试 T2 T3
11.20
2022.11.19 考试 T1
11.21
2022.11.21 考试 T1 T2 T3 T4
11.22
2022.11.22 考试 T1 T4
[ARC110F] Esoswap(构造 + 性质)
神必题。
性质 1:一个位置 \(i\) 在经过不超过 \(n-1\) 次操作之后肯定会变成 \(0\),且之前的每一次操作之后这个位置上的值都互不相同。
证明:设初始 \(p_i=x(x\ne 0)\),操作一次之后 \(x\) 会走到位置 \((i+x)\bmod n\) 上。接下来由于我们只操作位置 \(i\),那么 \(x\) 如果要回到位置 \(i\) 就只能进行交换 \(i\) 和 \((i+x)\bmod n\) 的操作,这个操作只有 \(p_i=x\) 的时候可以进行,然而我们已经把 \(x\) 换到了位置 \((i+x)\bmod n\) 上,所以 \(x\) 在之后不可能回到位置 \(i\)。
性质 2:如果 \(p_i=0\),那么操作 \(i-1\) 不超过 \(n-2\) 次之后会有 \(p_{i-1}=1\),再进行一次操作就会有 \(p_{i-1}=0\)。
证明:考虑性质 1 中操作的最后一步前的状态。
性质 3:如果位置 \([l,r]\) 中数的形态为 \(0,1,\dots,r-l\),那么操作 \(l-1\) 不超过 \(n-1\) 次之后,位置 \([l-1,r]\) 中的数的形态会变为 \(0,1,\dots,r-l+1\)。
证明:还是倒过来考虑,在 \(p_{l-1}=r-l+1\) 的时候会有 \(l-1\) 和 \(r\) 位置交换,然后 \(p_{l-1}\) 就会变成 \(r-l\),一直做下去就会得到 \(p_{l-1}=0\) 且 \(p_l=1,p_{l+1}=2,\dots,p_r=r-l+1\)。
那么我们只需要先把最后一个位置变成 \(0\),然后不断往前扩展有序的连续段就行了。
代码:
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n; cin >> n; cout << n * n << '\n';
for (int i = n - 1; ~i; i-=1) for (int j = 1; j <= n; j+=1) cout << i << '\n';
}

浙公网安备 33010602011771号