2023-2024 杂题乱补

文章探讨了在算法竞赛中运用贪心策略、点分治思想、期望计算、区间动态规划、位操作数据结构、2-SAT解决、子序列自动机、排序算法、线段树优化以及博弈论在特定问题(如GeneralizedSubtractionGame和贪吃蛇)中的应用,展示了复杂问题的有效解法和优化技巧。
摘要由CSDN通过智能技术生成

新坑。只收录题解。

P7215 [JOISC2020] 首都

Tags: 贪心,点分治

深刻地认识到自己根本没学会点分治。

考虑朴素的暴力怎么做:枚举一个点作为根,考虑求出令这个点的颜色全部连通所需要的最少合并次数。维护一个队列,每次将队首的父亲塞进队列,如果这使得队列里出现了一个新颜色,则将这个颜色的所有点塞进队列。记录过程中访问过的颜色个数,单次复杂度线性,总复杂度 \(\mathcal{O}(n^2)\)

贪心,发现有这样一条神奇的性质:如果以 \(y\) 为根时选到了颜色 \(x\),则以 \(x\) 为根的答案一定不会更劣。

换句话说:如果我们在统计点 \(x\) 时发现要选颜色 \(y\),但我们已经知道了以 \(y\) 为根的答案,则无需继续统计这个 \(x\)

考虑点分治。对于每个分治重心,我们只考虑它子树内的答案。如果有与根同色却不在子树内的点存在,这代表着更优的答案已经在之前的分治过程中被统计,满足上述情况,可以直接舍掉。

时间复杂度 \(\mathcal{O}(n\log n)\)

[ARC108E] Random IS

Tags: 期望,区间 dp,BIT

首先发现,在最终序列一样的情况下,标记的顺序会对后面的概率造成影响。也就是说 dp 数组需要维护整个标记序列,这显然是不可做的。

发现一条性质:对于 IS 来说,只要我们在其中选择了一对点 \((l,r)\),这个序列就被划分成了互不相关的三部分。在这两个点之间的所有位置怎么选不会被这两个点之外的数影响。这启发我们直接把选择的数塞进状态,设 \(f_{l,r}\) 表示已经选了 \(l,r\) 位置上的数,这个区间内期望还会选多少个数。

那么设 \(x\)\([l,r]\) 内可以选的点数,转移不难写出:

\[f_{l,r}=\sum_{a_l<a_k<a_r} \frac{1}{x}(f_{l,k}+f_{k,j}+1) \]

看起来取得了很大进展!但是直接转移是 \(\mathcal{O}(n^3)\) 的。

考虑倒序枚举 \(l\),正序枚举 \(r\),这符合区间 dp 的转移顺序要求。同时,对式子里的三项分别维护权值树状数组,时间复杂度 \(\mathcal{O}(n^2\log n)\)

CF1697F Too Many Constraints

Tags: 2-SAT

调不出来。调不出来。调不出来。

发现 \(k\le 10\),从值域上下手。第一个操作告诉我们差分约束是不可行的,考虑对所有变量拆点,设出共 \(nk\)bool 变量,其中 \(A_{i,j}\) 表示 \([a_i\le j]\)。这里不设为 \([a_i=j]\),是因为这样难以刻画「一些变量中恰有一个为 \(1\)」的条件。

那么对于同一个 \(i\),一定是一个前缀 \(j\) 的值为 \(0\),后面为 \(1\)。故连边 \(A_{i,j,1}\to A_{i,j+1,1}\),且有 \(A_{i,k}\) 恒为 \(1\)

序列递增,有 \(A_{i,j,1}\to A_{i,j-1,1}\)

对于 \(1\) 操作,实质上是保证 \(A_{i,x}=A_{i,x-1}\),即 \(A_{i,x,1}\to A_{i,x-1,1},A_{i,x,0}\to A_{i,x-1,0}\)

另外两个操作本质相同,这里以 \(2\) 操作为例:枚举一个变量 \(s\)。若 \(a_i>s\),则有 \(a_j\le x-s-1\)。连边即为 \(A_{i,s,0}\to A_{j,x-s-1,1}\)。当然,下标越界的情况要进行若干特判。

跑 2-SAT,时间复杂度 \(\mathcal{O}(nk)\)

[ARC110E] Shorten ABC

Tags: 子序列自动机,dp

没见过所谓「经典 trick」,这下孤陋寡闻了。

考虑用运算来刻画这个神奇的合并操作,令 \(\texttt{A,B,C}\) 分别为 \(1,2,3\),发现异或运算有一个同样神奇的性质:\(1\text{ xor }2=3\)\(2\text{ xor }3=1\),这恰好是让两个数变为它们之间没有的那个。

也就是说,我们的操作现在可以定义为:

  • 合并相邻的两个数,合并的结果为它们的异或值。

那么对于 \([l,r]\) 这一段,如果它们能全部合并至只剩一个数,结果也是确定的,为 \(a_l,\dots,a_r\) 的区间异或和。同理,不能合并的情况有两种:这一段均由同一种字母组成;区间异或和为 \(0\)

找完了操作的性质,考虑如何计数。将原序列分成若干段,每段分别合并成只剩一个字母,把它们拼起来即可得到一个操作后的序列。我们对前者进行 dp。

\(f_i\) 表示前 \(i\) 个位置已经合并完,得到的不同序列数。直接算显然会重复,这里使用类似于子序列自动机的思路,钦定转移时只使用最短的合法区间。
这样做的正确性依旧基于异或的性质,如果存在一个次短的转移区间,它的结尾一定有一个异或和为 \(0\) 的段。把这一段给右面的字母对结果没有影响。

\(g_{i,j}\) 表示以 \(i\) 为起点,第一个区间异或和为 \(j\) 的右端点。这同样可以倒序 dp 得出。
故有总转移

\[f_{g_{i,j}}\gets \sum f_{i-1} \]

时间复杂度 \(\mathcal{O}(n)\)。注意特判一开始所有字符都相同的情况。

CF1558F Strange Sort

Tags: *3300,排序,线段树。

考虑把对数列排序的问题转为对 \(01\) 排序,即枚举一个 \(k\),令 \(b_i=[a_i\ge k]\)
这样做的好处是只需要让所有 \(0\) 移到它应该在的位置上就可以了。设 \(b_i\) 表示从左往右第 \(i\)\(0\) 需要多少步归位。
那么分为两种情况:如果它左移过程中碰到了一个 \(0\),它只能在这个 \(0\) 的下一步归位;否则取决于途中遇到 \(1\) 的个数。

\[b_i=\max (b_{i-1}+1,k_i+p_i\bmod 2) \]

显然我们只关心最后一个 \(0\) 的归位时间,而不是整个序列。因此将这个式子拆开,有

\[f_i=\max\{k_j+(p_j\bmod 2)+i-j\} \]

从小到大依次将序列中的 \(1\) 修改为 \(0\),这个式子是可以用线段树维护的。答案即为过程中的最大值。

CF1556G Gates to Another World

CF1286F Harry The Potter

[BalticOI 2002 Day2] L Game © Edward de Bono

Tags:博弈论,大模拟。
调了 2.5h,比去年省选的时候有点进步 /cf

一眼看起来是有三种胜负态的爆搜,神似 [省选联考 2023] 过河卒给题解打广告 >w<

题里告诉我们状态数只有 \(18000\) 多种,也就是说直接暴力把状态转移图建出来是可行的。但是有环怎么判平局呢?
倒序拓扑转移,一个点可以入队当且仅当:

  • 它存在至少一个必败的出边;
  • 它所有的出边胜负态都已经被确定。

依次确定胜负状态,若初始状态最后仍没有入过队,则平局。
然后剩下的就是大模拟了。卡常技巧:

  • 对于 L 形棋,相比于记录一个点和 \(8\) 个方向(这有大量的不合法需要特判),记录 \(2\times 3\) 的矩形和 \(4\) 个方向是更优的选择;
  • 用二进制而不是奇怪 hash 对状态直接压缩;
  • map 很慢,如果用了奇怪 hash 建议手写哈希表;
  • 两个中立点没有差别,钦定坐标较小的在前面可以减少一半状态;
  • 钦定第一个棋子为先手,省去记录先后手的两倍状态常数。

[ABC278G] Generalized Subtraction Game

Tags: 博弈论

我们有时候要把正经做法和人类智慧结合起来。—— KH

试试直接 dp?设 \(sg(i)\) 表示一个长度为 \(i\) 的区间的 \(\text{SG}\) 值,总复杂度是 \(\mathcal{O}(n^3)\) 的,且看起来不可优化。

找性质,如果在第一步能把区间划分成恰好相等的两段,此后不论后手做什么,我们都在另一段做对称的操作,就赢了。

而做不到这种情况时,必然有 \(l=r\)\(l\bmod 2\neq n\bmod 2\)
进一步地,重新考虑上面的 dp,因为这时候有 \(l=r\) 的保证,转移复杂度就变成了 \(\mathcal{O}(n^2)\)。具体地,可以写出方程:

\[sg(i)=\text{mex}_{j+k=i-l} \{sg(j)\oplus sg(k)\} \]

求出先后手必胜后,选择对应的一方即可。

至于构造方案,类比 Nim 游戏的证明,对每个 \(i\) 开一个桶存放哪些位置能被谁异或到,每次枚举连续段,找到任意一个使 \(\text{SG}\) 值变为 \(0\) 的方案并操作。
因为这里可以接受平方的复杂度,每次重新统计所有连续段情况是完全可行的。

[CSP-S2020] 贪吃蛇

传世经典!

介绍一个简单但复杂度没啥保证的做法。我们先从头到尾模拟一遍这个过程,并记录每个时刻是谁吃了谁,每条蛇是在什么时候被吃掉的。

从后往前倒推,考虑当前的时刻为 \(i\),最大的蛇为 \(ld_i\),它将在 \(tim_{ld_i}\) 时刻被吃掉。
设当前的答案,即结束时刻为 \(ans\)
如果这条蛇到 \(ans\) 之前它都不会被吃掉,它这一步吃别人显然不亏;如果 \(tim_{ld_i}\le ans\),也就是如果不更新答案它就要死了,它一定选择不吃,即 \(ans\gets i\)

那么我们只需要考虑清楚怎么预处理这个过程就好了。直接用一个 set 之类的东西维护是带 \(\log\) 的,据说有人卡常跑过了,但樱雪喵亲测完全过不去,怎么回事呢。
发现一个性质:每次进行吃蛇操作后,产生新蛇的长度单调不增。考虑维护两个 deque,一个存原序列,一个存合成的新蛇。那么它们两个分别单调,时间复杂度 \(\mathcal{O}(Tn)\)

于是你发现假了。为什么呢?
虽然长度确实是单调不增的,但蛇的编号并不是这样。这造成了看起来判断了编号,实际上啥也没判的问题。一个可行的解决方案是利用 deque 支持随机访问的性质,手动把新值 swap 到编号正确的位置上。虽然这么搞的时间复杂度没有保证,但实际运行起来可以通过。

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值