随机乱做 Part 8
- [CSP-S 2022] 星战(随机化 + 哈希)
- [CSP-S 2022] 数据传输(DP + 点分治 + 矩阵)
- [IOI2020] 嘉年华奖券(贪心 + 构造)
- [ARC108E] Random IS(区间 DP + 树状数组 + 概率期望)
- [洛谷 P5391] [Cnoi2019] 青染之心(背包 + 树链剖分优化空间)
- [洛谷P4616] [COCI2017-2018#5] Pictionary(树链剖分 + 并查集 + ST 表)
- [ARC108F] Paint Tree(直径的性质)
- [AGC001C] Shorten Diameter(直径的性质)
- [ARC064F] Rotated Palindromes(组合计数)
- [洛谷 P2470] [SCOI2007] 压缩(区间 DP)
[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/。
[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/。
[洛谷P4616] [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/。
[洛谷 P2470] [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})\)。