总结
-
2025.7.8
上午把分治往后备了一点,学习了根据最值分治的套路,下午在消化昨天的内容,把线段树的半群模型入了一点门.
晚上听课,把之前一直不明白的回滚莫队和二次离线搞明白了,还提出了关于奇偶化排序的一个问题,和大家讨论后收获不小.
课后做了回滚莫队的模板,又去补了几道之前没过的题
根据最值分治:
式子中有最值,要求计数或算贡献,考虑以区间最值为分治中心,计算跨区间贡献,来固定式子中的一项,被子区间完全包含的递归处理,注意边界处理.
由于分治中心分出的两个子区间长度不定,所以采用启发式分治的思想,枚举长度短的一侧,计算与另一侧的贡献. 时间复杂度 \(O(n \log n \times f(n))\), \(f(n)\) 为处理跨区间贡献的复杂度
2025.7.10
不知道
2025.7.18
2-sat
k-sat 是 npc 问题,不可做
2-sat bool 变量可达性问题
用 tanjan 缩点做到 \(O(n+m)\) 的复杂度
定义 :
-
\(i\) 代表 \(x_i\) , \(i+n\) 代表 \(\neg x_i\)
-
当 \(x_i\) 成立时 \(x_j\) 一定成立,称 \(x_i\) 蕴含 \(x_j\)
-
蕴含具有传递性,用图论建模解决
连边:
当 \(x_i=0\) , \(i \to \neg i\)
当 \(x_i=1\) , \(\neg i\to i\)
当 \(x_i\vee x_j=0\) , \(i \to \neg j\) , \(j \to \neg i\)
当 \(x_i\wedge x_j=1\) , \(\neg i \to j\) , \(\neg j \to i\)
算法流程:
如果 \(x\) 可达 \(y\) ,则 \(x\) 可推出 \(y\) .
如果 \(x\) 与 \(x+n\) 在同一强连通分量,无解.
通过互反变量的拓扑序大小判断变量的真假,从而构造方案.
例题
\(x\) 和 \(y\) 有连边 , \(\neg x\to y\) , \(\neg y \to x\)
\(x\) 和 \(y\) 在同一部分,\(x\to \neg y\) , \(y \to\neg x\)
前后缀优化建图,使边数降低至 \(O(n)\) 级别.
8.31
P7078
贪心的想,一条蛇在什么时候会选择结束游戏。
当最强蛇吃掉最弱蛇后不会变为最弱蛇,新的最强蛇的长度一定没有原来的长,最短蛇一定没有原来短,所以新的最强蛇无法吃掉之前的最强蛇。这时他就可以放心地吃。
如果他变成了最短蛇,考虑下一条最强蛇是否敢吃他,如此递归考虑,直到出现了一条可以放心吃的蛇,再回推出当前最强蛇是否敢吃即可。
用 set 维护蛇的长短,依照上述过程模拟即可。
时间复杂度 \(O(n \log n)\)
P5021
首先看最小值最大想到二分答案,考虑如何判定是否合法。
在一个结点的路径,有三种归宿,
其一为与子树内的其他路径喜结连理,共同组成赛道。
其二为向上延伸,与更高的子树内的路径组成赛道。
其三为打一辈子光棍。
我们首先让子树内尽量配对,再在剩下的路径中选一条向上延伸,最后判断路径条数是否达标即可
时间复杂度 \(O(n \log^2 n)\)
9.1
P8632
简单斜率优化,先写出朴素DP方程 \(f_{i,j}\) 表示前 \(i\) 个村庄修建 \(j\) 个会点的最小距离
其中 Val 为 贡献系数
直接拆成直线解析式的的形式,因为第二位只有 \(4\) 种取值,所以我们直接用 \(4\) 四颗李超树维护就行了。每次从 \(j-1\) 那颗树上转移就行了。
时间复杂度 \(O(n\log n)\)
拉格朗日插值
P7116
考虑把所有点向某个方向的行为看成这个场馆往反方向平移。每次操作后答案加上当前剩余的点数。显然没次剩余的点数都应该是一个 \(k\) 维立方体。其中有一维为 \(0\) 时退出。
我们把每一维的变化量 \(\Delta\) 暴力算出来,并将它取整,这样左端点不再变化,只有右端点变化,且
长度的变化量为一个关于轮数的一次函数,那么答案就是每一位长度乘积的前缀和,是一个 \(k+1\) 次函数,我们暴力求 \(k+1\) 轮的答案,作为点值插值,求出答案的函数,代入一个最小轮数就能处理出所有整轮的答案,最后在暴力跑剩余的部分即可。
9.2
模拟赛
A
若至提,考虑对区间造成贡献的只有最值,直接按最值分治,考虑有多少种区间长度,用差分维护一下就可以了。 复杂度 \(O(n\log n)\) (考场上写了棵线段树凭空多了一只 \(\log\))
讲了 \(O(n)\) 做法,只用差分就行了
B
一个直观的想法就是比较组合数的大小,但是太大了,存不下。
所以我们可以通过比较 \(\log\) 的大小来比较原数的大小。
这样就有了一个 \(O(n^2)\) 的做法。考虑优化,发现在选的数一定时,总数越大,结果越大。
所以我们先只在 总数为 \(n\) 的组合数中选择,选走一个组合数后再将总数-1 加入。
时间复杂度 \(O(k)\)
C
中间忘了
D
把每个事件向后面第一个难忘的事件连边,可以构成一棵树的结构,用树上主席树维护 \(val\) 即可,
时间复杂度 \(O(n \log n)\) 。
9.3
P3960
小清新DS题,观察题目发现离队操作只会影响当前行和最后一列的队形,所以考虑用数据结构维护每一行和最后一列的信息。
考虑我们需要怎样的数据结构,单点插入/删除,全局 \(k\) 大值,我们可以选择用平衡树暴力维护,但不好写,常数又大。考虑动态开点线段树,它可以完成除了插入之外的操作,但由于本题的插入只在尾部进行,所以我们可以在尾部多开一倍空间,插入就直接向后插入,其他操作简单维护即可。
但初始信息是 \(n^2\) 级别的,直接初始化会爆炸,所以我们定义空节点为初始状态,只有被删除或后来插入的信息建立节点,这样的复杂度是 \(O(q \log n )\) 的可以通过此题。
P3959
状压DP,容易发现最后的答案会形成一棵树,而一条边的花费与它起点的深度有关,
所以可以设 \(f_{dep,S}\) 为树深为 \(dep\) ,已选集合为 \(S\) 的最小花费。
容易得到转移方程 \(f_{dep,S}=\min_{T\in S} f_{dep-1,T} + dep * cost(T,S)\)
其中 \(cost(T,S)\) 为将集合 \(T\) 转移为 \(S\) 需要连的边的边权。
枚举起点,依照上式转移即可,时间复杂度 \(O(3^n n^2)\)
P4001
算法:图论建模(平面图转对偶图), 最短路
观察题面,题面给了一个网格图,还带边权,显然是最小割,可以转成对偶图然后跑最短路,跑完之后就是最小割,具体的:给每个格子编号,确保网格图中的边每条都恰好被一条对偶图中的边切,对偶图中的边权就是所切的网格图中的边的边权
注意:看清对偶图中的终点的编号是什么!!!
9.4
P1600
树上差分,考虑什么样的节点可以被统计到,令一条路径的起点和重点分别为 \(s\) 和 \(t\) 。
当 \(s\) 为 \(t\) 的祖先时,满足 \(dep_x-dep_s=w_x\) 的点可以被统计到,
当 \(t\) 为 \(s\) 的祖先时,满足 \(dep_s-dep_x=w_x\) 的点可以被统计到,
否则,令 \(s\) 和 \(t\) 的 lca 为 \(p\)
当 \(x\) 在 \(p\) 到 \(s\) 上时,满足 \(dep_x-dep_s=w_x\) 即可
否则 满足 \(dep_x+dep_s-2*dep_p=w_x\) 即可
把相应的深度插入对应的线段树,用线段树合并做一个类似前缀和的东西就行了。
时间复杂度 \(O(n \log n)\)
P4175
循序渐进的考虑问题,如果在序列上,直接主席树即可,搬到树上,用树上主席树维护根链信息,树上差分维护即可,如果带修的话,可以树剖+树套树 在 \(O(n\log^3 n)\) 的复杂度内解决。
考虑更优的解法,由于只维护根链信息,所以树剖是不必要的,可以尝试用树上差分解决,
考虑增加/减少一个数,会对它子树内的根链信息造成影响,所以我们需要一个支持子树修改,单点查询的数据结构维护,子树修改可以用 dfn 序拍成区间修改,于是我们就可以使用树状数组套线段树+差分以 \(O(n \log^2 n)\) 的复杂度解决此题,据说还有神秘单 \(log\) 算法。
9.5
P10375
简单题,求一个序列的中位数可以线段树上二分,题目要求求两个序列上的中位数,我们直接对每个序列都开一个线段树,询问时将询问的两颗线段树一起二分就行了。
注意离散化一下,要不然空间复杂度会达到 \(O(n \log n)\) ,会被卡。
P3730
要求求区间内出现次数的 \(k\) 小值,发现单纯使用主席树不是很好差分,所以退而求其次,考虑单根算法,用莫队维护区间出现次数,用值域分块维护 \(k\) 小值,选用 \(O(1)\) 修改的值域分块,根号平衡,总时间复杂度 \(O(n \sqrt{n})\) 。
注意特殊考虑区间内出现次数为零的数。
P4867
发现要求维护的信息不可差分,考虑根号算法,用莫队套值域分块,只在出现次数由零变一或一变零是向值域分块中插入/删除元素,查询操作相当于在值域上的求和,是容易做的。根号平衡后时间复杂度为 \(O(n \sqrt{n})\)。
注意不寻常的空间限制,调整块长以降低空间开销。
双倍经验 : P4396
9.6
A P3760
看到异或运算,考虑按位考虑贡献,用前缀和优化,化一下式子。
考虑减法的按位意义,发现与借位有关,分 0/1 的情况讨论,用树状数组维护即可。
B
不知道
C P5443
没有修改操作时是 kruscal 重构树裸题,有了修改操作不是很好维护,考虑根号算法,根号重构,对每 \(sqrt{n}\) 个询问进行一次暴力重构,将边按权值排序后,将无修改的边加入边集,修改的边将最新的权值加入,用可撤销并查集维护这个过程。
9.7
P3730
要求求区间内出现次数的 \(k\) 小值,发现单纯使用主席树不是很好差分,所以退而求其次,考虑单根算法,用莫队维护区间出现次数,用值域分块维护 \(k\) 小值,选用 \(O(1)\) 修改的值域分块,根号平衡,总时间复杂度 \(O(n \sqrt{n})\) 。
P6406
题目要求维护的是一个 \(\min\) ,\(\max\) ,\(len\) 的乘积的和,遇到这种带最值的序列统计贡献问题一般会考虑分治,我们对区间进行分治,在每层计算跨过区间中点的贡献。
我们令 \(\min\) 的取值一定来自左区间,枚举一个 \(i\) 作为区间左端点,同时分别维护 \(j\) ,\(k\) ,分别为满足 \(\min\) ,\(\max\) 都来自左区间,和只有 \(\min\) 来自于左区间的最右端点。
右端点在 \(mid+1\) 到 \(j\) 的贡献是好算的,因为 \(\max\) ,\(\min\) 都已确定,区间长度是一个等差数列,高斯求和就行了。
考虑右端点在 \(j+1\) 到 \(k\) 的贡献,发现是一个类似
的式子,其中 \(i\) 相关项均为常数。我们只需要再维护一个前缀 \(\max\) 与位置乘积的前缀和数组就可以 \(O(1)\) 解决。
\(\min\) 在右区间的情况是一个相似问题,相似处理就可以了。注意溢出。
时间复杂度 \(O(n\log n)\)
TTM
可持久化线段树+标记永久化 板子
在区间被完全包含时,仍然正常打标记,但不下传,在查询时加上一路上的标记就行了。
9.8
P7475
题目与前缀有关,联想到 trie 树 ,发现就是求子树中的 \(k\) 大值,有两种做法,一种是直接用树上主席树,每个节点的的树都维护子树信息,修改就了修改根链,由于题目给出的链长度不超过十,所以是对的, 时间复杂度 \(O(n \log n)\)
还可以直接用 dfn 序拍扁成区间问题,直接上树套树就行了,时间复杂度 \(O(n \log^2 n)\) 。
9.9
P5522
用线段树维护满足每个区间要求的字符串,合并时分情况讨论,第 \(n+1\) 位表示是否存在合法字符串,双半群合并即可,时间复杂度 \(O(nq \log m)\) 。
不是正解,但哥们常数太小了,直接过了。
P8250
从零开始的根号生活,发现这个共同邻居很难维护,同时整张图的度数是一定的,于是考虑对度数根号分治,先说说暴力怎么写吧。
暴力一 :暴力扫两个点的邻居,打标记判重即可。时间复杂度 \(O(q m)\) 其中 \(m\) 为度数
暴力二 :维护每个点的邻接点的 bitset ,询问时直接取交处理即可,时间复杂度 \(O(\frac{nq}{w})\),空间复杂度 \(O(n^2)\)
我们发现暴力一与度数有关,暴力二的空间复杂度有些爆炸。
于是我们按度数将点分为轻点和重点,询问到轻点直接遍历,重点用 bitset 预处理,时间复杂度很对。
9.10
模拟赛,写写会的题吧。
A
发现当删除某个数时,其他的数的操作次数只可能增加,不可能减少,而每次一定是取最大值操作最优,所以我们倒过来做,用线段树维护一下最值即可,时间复杂度 \(O(n\log ^2 n)\)
P7811
根号分治好题,这种取模的题一般就是根据模数的大小分类了,
考虑 \(k\) 较小时,我们可以预处理较小的所有模数序列,做 \(RMQ\) 即可(因为卡空间,所以用分块做到 \(O(n) 空间\))。
考虑 \(k\) 较大时,模数的循环节较长,所以我们可以在循环节上做文章,
我们可以跳循环节,用莫队维护区间,找到对应区间后用 bitset 暴力跳循环节以及每个循环节的最小值即可。
卡常卡司了
9.11
P13925
合成大西瓜,考虑 \(dp\)。枚举右端点 \(i\) 与左端点 \(j\),如果 \(i\) 到 \(j\) 可以合并就 \(dp_j+1→dp_i\) 。
考虑固定 \(i\) 时如何快速枚举 \(j\)。注意到假设相邻两个合法的 \(j\) 分别为 \(k′\),\(k\),那么必有从 \(k′\) 到 \(k−1\) 合法且这一段合并的结果与 \(k\) 到 \(i\) 相等。
于是用一种类似倍增的方法,记录可以从哪里过来。容易发现这样复杂度是对的。最劣的情况大概是全相等的时候,转移次数 \(O(n \log n)\)。
CF1436E
绝世好题啊,考虑 \(mex\) 的求法,发现极短的 \(mex\) 段最多是 \(O(n)\) 级别的,证明大概是这样的 :
枚举值域,每个数可能作为 \(mex\) 的区间一定不包含他本身,所以序列就被划分为了 这个元素出现的个数+1 段,对所有这样的区间计数,其总数就是 \(O(n)\) 的。
也就是说,我们只对这 \(O(n)\) 个区间求 \(mex\) ,就能得到全局所有子区间的 \(mex\),最后再对 \(mex\) 集合暴力求一遍 \(mex\) 即可。
维护过程如下:
我们维护每个数上一次出现的位置,每次扫到这个数时,就把 \((las_{a_i},i)\) 这个区间的 \(mex\) 求出并记录。同理我们再倒着跑一边,记录每个数到它后一次出现位置的 \(mex\) 。这样就能统计所有子区间的 \(mex\) ,最后扫一遍值域求 \(mex\) 的 \(mex\)
中间求区间 \(mex\) 可以使用主席树 ,时间复杂度 \(O(n \log n)\) 。
相同 \(trick\)
9.12
进行一个初赛的考,知识点如下:
g++ 编译命令 :
-
显示编译过程中的详细信息
-v -
启动优化选项
-O2 -
指定输入可执行文件的名称
-o
费马小定理 需满足 p 不是 a 的因数
哈希的 base 选择:在模意义下大于值域,最好为质数,但不一定要是。
欧拉图(含欧拉回路)所有顶点的入度等于出度
只有欧拉路径的叫半欧拉图。
用数列求和算复杂度时,不要再算循环层数,最好找到类似的已学算法来推算复杂度。
9.13
おくすり飲んで寝よう
之前模拟赛的题,诈骗题,容易发现每次只需要让较大的减少,较小的增大,一定能保持最优决策,按照这个策略模拟即可。
开局不利
判有无解可以按截止时间排序,如果有所需时间大于截止时间时,说明无法无伤;
考虑如何操作能将伤害最小化,发现相同的时间,推惩罚系数大的箱子减少的伤害更多,所以按惩罚系数降序排序,尽量让每个箱子推完的时间靠后,防止挤占截止时间靠前的箱子。
现在问题转化为如何:
-
计算在截止时间前能推多少次箱子
-
将一段时间标记为已占用
-
找到一个尽量靠后能满足当前箱子需求的区间
我们可以用线段树维护上述操作,每个时间段为 \(1\) 代表已占用,否则为未占用。 计算推箱子次数相当于求出截止前有多少个时间没被占用,用总共的减去占用的即可,可以用区间求和解决。2 操作就相当于区间赋值,3 操作可以二分求得,因为区间越靠前,可用的时间段一定不会减少。综上,我们实现了一个 \(O(n \log^2 n)\) 的算法维护此过程。
二分好像没办法搬到线段树上,所以多一只 \(\log\) ,理论可过。
9.14
爬行的 GCD
数学题?发现只要元素的总和大于长度为 \(n\) 的某个理想序列,那么就可以不删元素,只凭加减来变成理想序列,所以问题转化为总和最小的理想序列,显然是由纯质数组成的序列,所以提前筛一下质数,枚举序列长度即可。
爬行合并
首先考虑简单的暴力,容易发现将每个串向组成他的串连边,这是一张 DAG ,在拓扑的时候判断答案应该在哪个儿子里,直至到了 0 或 1。
考虑优化,用倍增和重链剖分的思想,求出每个串只走较大的一边所到的串有哪些,以及到那里需要 查询的位置是多少,每次跳到不能跳时就走一次轻边,走轻边每次串长至少减少一半,最多走 \(\log\) 次,内层倍增是 \(\log\) 的,总时间复杂度 \(O(n\log n)\) 。
晚上一个初赛的考,知识点 :
-
ls查看目录下的内容 -
cd切换工作目录 -
cpcopy -

-
char占八位,但是有符号的,范围-128~127 -
不要用 \(r\) !!!
-
放个 LInux 命令表
9.15
P4215
线段树典题,发现孩子的要求无法在每次操作后都扫一遍,考虑如何只遍历操作对其有影响的询问,我们把询问挂在线段树上,每次单点修改时,只检查所涉及的链上的询问,对每个询问,我们维护他在多少个区间中出现,如果某次修改后值为零,则更新答案。
9.16
模拟赛
T1
构造+结论题,打表发现最多操作 \(2\) 次,所以直接分别处理操作次数不同的情况,当逆序对数本就符合要求时显然时 \(0\) ,如果有一个区间的逆序对数等于原逆序对数与 \(k\) 的差,那么这个区间就是答案。当需要操作两次时,我们优先选逆序对较多但不大于原逆序对数减 \(k\) 的区间,第二次选择一个恰好的区间。因为题目保证有解,所以是对的。上述找区间的过程可以使用双指针+权值树状数组解决。
T3
令 $f_{i,j} $ 为是否能做到 \(s\) 匹配到第 \(i\) 位,\(t\) 匹配到第 \(j\) 位,转移有两种情况,一是上下匹配,转移式为
另一种是题目中的插入两个相同的字符,转移式为
这样就写出了 \(O(nm)\) 的暴力,发现转移可以用 bitset 优化,用 bitset 存 \(f\) 的滚动数组和每个字符在 \(s\) 中出现的位置 \(pos\),转移一就优化成了
转移二就优化成了
时间复杂度 \(O(\frac{nm}{w})\) ,可过。
9.17
发现选择集合与顺序无关,所以将 \(a\) 和 \(b\) 升序排列后考虑。
我们并不关心如何让选出的集合合法,那么如果我们已经确定了选择哪些元素,最好的方法就是将 \(b\) 的前 \(k\) 个与 \(a\) 顺次配对,后 \(n-k+1\) 个同样如此。
我们可以设计一个 \(O(n^3)\) 的动态规划,枚举选出集合的大小。
设状态:
转移如下:
由于 \(k\) 是转移所需的参数,必须枚举,因此复杂度过高。
考虑优化,发现正序考虑选入集合和逆序考虑不选入集合是对称的。
我们希望将一个前缀和一个后缀拼接起来,但并不是每个拼接点都合法。
对于每个 \(k\),我们希望拼接点 \(p\) 满足:
因此,第一个满足 \(a_i > b_k\) 的位置 \(i\) 就是拼接点。
9.19
candy
首先有一个重要的性质,每个数模小于自己的数,至少减小一半,所以我们只存每个数下一个比他小的数的位置,倍增就能快速找到答案。
考虑将询问的区间转为前缀和,记 \(S_p(x)=∑x_i=F_p(x)\) ,转移为记 \(q\) 为 \([p,n]\) 中第一个比 \(x\) 大的 \(a_q\),\(S_p(x)=\lfloor \frac{x}{a_q} \rfloor {a_p} S_q(a_q-1)+S_q(x \mod a_q)\),预处理出 \(S_p(a_p−1)\) 后即可 \(\log n \log V\) 完成查询。
9.20
P3988
发现其实就是一个找第 \(k\) 大的操作,然后销牌的操作其实是一个循环位移的过程,维护一个偏移量,表示应该发那张牌,对当前牌库中牌的数量取模即可。
SP2916
分两种情况讨论,分为两端点区间是否相交;
若不相交,答案组成其实就是左边的最大后缀和+中间的和+右边的最大前缀和,用类似小白逛公园的线段树维护一下就可以了。
如果相交,答案有三种可能,一是在中间重合区域直接求最大子段和,二、三分别是左/右端点在各自区间内自由,另一个端点在未重合区域的答案。
9.20
檐牙覆雪
开局没思路,先想暴力,单次 \(O(n \ln n)\) 的做法是显然的,通过打表发现每个点的最佳转移点是他的他除以他的最大质因数,提前预处理一下,可以通过 \(50\) 分的高分。
现在开始想正解,我们把每个点向他的最佳转移点连边,最终是一颗树的结构,每个点的答案就是根链上的贡献。每次转移时顺便做一个子树加就行了,预处理出来就很显然了。
美好的每一天
考虑回文子串的定义,发现回文串中最多有一个字符出现奇数次,考虑有什么东西偶数可以消掉,异或拥有类似的性质,我们发现字符集大小不大,所以直接用一个二进制位代表一个字符,这个东西是经典的莫队问题,前缀异或一下,增量时查询有多少区间满足和当前量异或和为 \(0\) 即可,这样就解决了偶回文串的问题。
考虑奇回文串,我们可以枚举是哪个字符出现了奇数次,查询这样的区间是否存在就行了 复杂度 \(O(26 \times n\sqrt{n}))\)
9.21
模拟赛,哈哈哈哈。
T1
递推基础练习题,式子花费 eps 秒推出,花费 eps 分钟写完矩乘优化,简单。
T2
基础哈希练习题,二分合法长度,暴力 check 即可,为了稳妥写了个双哈希,最后发现大样例过不去,以为是常数太大了,没想到是数组开小了,🤣🤣🤣。喜挂 \(60 pts\) 。
T3
简单差分约束题,但 \(50ms\) ,变成困难 \(DP\) 题,令 \(f_i\) 为 \([1,i]\) 已经合法的最大数量,维护 \(i\) ,位置可以转移的区间,用单调队列优化即可。最后判无解即可。
T4
根号题滚出 oi ,😡😡😡。
先看特殊性质,发现 \(a\) 对 \(b\) 取模后并不影响答案,所以直接预处理出所有 \(a,b\) 对应的前缀和,\(O(1)\) 回答询问即可。
考虑正解的空间限制有限,考虑只维护每个块的答案,\(O(\sqrt{n})\) 回答询问。
预处理过程如下:
定义函数:
若 $ f(x) = f(y) $,令:
情况一:$ c = 0 $
要求:
等价于:
注意:这两个区间不重叠。
情况二:$ c = 1 $
要求:
等价于:
情况三:$ c = -1 $
要求:
等价于:
上述所有限制都是关于 $ t $ 的区间,且满足 $ t < b $。因此可以在值域为 b 的桶上进行差分处理,再通过前缀和快速查询整块内满足条件的对数。
我们成功做到了 \(O(nb+b^{2}\sqrt{n})\) 预处理,\(O(\sqrt{n})\) 回答单次询问的优秀做法。
9.22
模拟赛,一道不会,🤣🤣🤣,☺️☺️☺️,退役吧。
9.23
lucky
考试开始三小时后改交互格式,资本你赢了。
设 $ S $ 为所有 2 的非负整数幂组成的集合,即:
可证明以下核心结论:
当且仅当 $ n \in S $ 时,当前轮次操作者(先手)必败;
当 $ n \notin S $ 时,设 $ w $ 为 $ n $ 在二进制表示下「最低位 1 对应的权值」(即满足 $ 2^w \mid n $ 且 $ 2^{w+1} \nmid n $ 的非负整数),则:
先手取 $ 2^w $ 个时必赢;
先手取少于 $ 2^w $ 个时必输。
当前轮次操作者称为 “先手”,另一方称为 “后手”。
采用数学归纳法,对 $ n $ 的取值进行归纳验证。
-
基例:$ n=1 $
因 $ 1 = 2^0 \in S \(,此时先手需取至少 1 个(仅余 1 个),取后后手无数量可操作,符合 “\) n \in S $ 时先手必败” 的结论;
若假设 $ n=1 \notin S $ 的情况不存在(因 $ 1 \in S $),故基例结论成立。 -
归纳步:设 $ n>1 $,且对所有小于 $ n $ 的正整数结论均成立,证明对 $ n $ 结论成立
设先手在本轮取 $ x $ 个(满足 $ 1 \leq x \leq n $),记剩余数量为 $ n' = n - x \((后手操作时的初始数量),\) w' $ 为 $ n' $ 在二进制下的最低位权值(即 $ 2^{w'} \mid n' $ 且 $ 2^{w'+1} \nmid n' $)。分两种情况讨论:
情况 1:$ n=2^w $(即 $ n \in S $,因 $ 2^w = 2^k $ 对应 $ w=k $)
无论先手取任意 $ x \((\) 1 \leq x \leq n $),由归纳假设:
后手面对的剩余数量 $ n' = n - x $,其最低位权值为 $ w' $,且必有 $ x \geq 2^{w'} $(因 $ n=2^w $ 是 2 的幂次,二进制仅含一个 1,$ x $ 需覆盖 $ n' $ 的最低位,故 $ x $ 不小于 $ 2^{w'} $);
后手可直接取 $ 2^{w'} $ 个,使后续轮次的先手陷入必败态(符合归纳假设中 “非 $ S $ 集合数的必胜策略”);
因此,当 $ n=2^w \in S $ 时,先手必败。
情况 2:$ n>2^w $(即 $ n \notin S $)
需分别证明 “先手取 $ x=2^w $ 必赢” 与 “先手取 $ x<2^w $ 必输”:
若先手取 $ x=2^w $:
此时剩余数量 $ n' = n - 2^w $,分两种子情况:
子情况 2.1:若 $ n' \in S $(即 $ n'=2^k $):}
由结论 1,后手面对 $ n' \in S $ 必败,因此先手赢;
子情况 2.2:若 $ n' \notin S $:}
因 $ n>2^w $ 且 $ x=2^w $,故 $ n' = n - 2^w > 0 $,且 $ n' $ 的二进制最低位权值 $ w' > w \((\) 2^w $ 恰好消去 $ n $ 的最低位 $ 2^w $,剩余 $ n' $ 的最低位权值更高);
由归纳假设,后手需取 $ 2^{w'} $ 个才能赢,但 $ 2^{w'} > 2^w = x $ 且 $ n' < n $,导致 $ 2^{w'} > n' $(后手无法取到足够数量),故后手必败,先手赢。
综上,先手取 $ x=2^w $ 时必赢。
(2) 若先手取 $ x<2^w $
设 $ b $ 为 $ x $ 在二进制下的最低位权值,因 $ x<2^w $,故 $ b < w $;
剩余数量 $ n' = n - x $ 的二进制最低位权值 $ w' = b \((\) x<2^w $ 无法消去 $ n $ 的最低位,$ n' $ 的最低位由 $ x $ 决定);
由归纳假设,后手可直接取 $ 2^{w'} = 2^b $ 个,使后续轮次的先手陷入必败态;
因此,先手取 $ x<2^w $ 时必输。
由基例($ n=1 \()与归纳步(\) n>1 $)可知,对所有正整数 $ n $,原结论均成立。
water
怎么又是根号题,😒😒😒
首先写出暴力,打表观察到答案只可能是 \(1/2\) ,所以直接分成两张图来做,由于要求字典序最小,所以只用维护第一张图,无法放进第一张图的边直接丢进第二张图就好了。
考虑如何维护第一张图,发现其实就是一个 DAG 可达性问题,常见的套路是 bitset 维护,加上根号重构/分治。我们这里采用根号重构,每一定条边直接重新拓扑一遍更新,一个块内的边就用 bitset,暴力判断是否能加入。
复杂度 \(O(\frac{m^2}{B}+\frac{nm}{w}+\frac{mB^2}{w})\)
9.24
上升子序列
考虑新增一个数时对答案的贡献,其实就是以小于他的数为结尾的上升子序列的方案数,因为相同的子序列只算一次,所以用线段树维护以每个数结尾的子序列方案数,更新时直接覆盖掉旧答案即可,这样可以防止算重,初始为了方便,我们先算上长度为一的子序列,在最后统计答案时减去即可。
TEST_34
看到任意选数异或求最大值,自然想到线性基,但是区间异或操作并不好维护,于是我们进行一个前缀异或,修改时用类似差分的操作维护即可。
接下来我们简述如何查询并证明其正确性。
两个线性基等价当且仅当他们能表示出的集合相同,前缀异或后的数组,可以通过后一项异或前一项得到原值,所以我们查询 \([l+1,r]\) 这个区间内的线性基,就可以得到除了 \(a_l\) 和 \(a_{l+1}\) 之外的所有 \(a\) ,\(a_l\) 可以通过 \(\oplus_{i=1}^{l} b_i\) 来求得,直接将他插入询问取得的线性基,就得到了等价于 \(a_l,a_{l+1} \dots a_r\) 的线性基,在这个线性基查询就可以了。
时间复杂度 \(O(n \log n \log V)\) 。
9.25
跑步
分拆数板子,考虑 dp ,设 \(f_{i,j}\) 为前 \(i\) 个数选出总和为 \(j\)的方案数,容易发现这是一个完全背包求方案数问题,转移方程 \(f_{i,j}=f_{i-1,j}+f_{i,j-i}\) ,注意正序枚举即可。
Calc
继续考虑 dp ,设 \(f_{i,j}\) 为前 \(i\) 个数,在 \([1,j]\) 范围内的权值。答案即为 \(f_{n,k}\) ,转移方程也比较好写, \(f_{i,j}=f_{i,j-1}+j \times f_{i-1,j-1}\) ,但是过不了。
考虑继续优化,发现答案是一个多项式函数,所以直接拉插优化,复杂度 \(O(n^2)\) 。
9.26
子集卷积
没有交集为空的限制就是一个并卷积的形式,考虑如何加上这一限制,我们把每个集合按集合大小分类,每个位置应该受到集合大小相加等于当前集合大小且并集为当前集合的贡献,我们只给 \(f_{popcnt(S),S}\) 赋值,确保不会有错误的贡献被计算,然后做一个并卷积就好了。
然后是模拟赛。
矩阵
诈骗题,除了随机撒点检验都能过,下面是我的方法,
考虑利用前缀和能够快速算出 \(A \times B\) 每一行和每一列的和,把他和 \(C\) 的相应行列比较,大概率正确。
但我们发现将两条对角线互换就能卡掉他,所以我们再暴力检验两条对角线,再随机撒点验证即可。
长野原龙势流星群
贪心题,考虑一个权值最大的点,一定只选他自己,而他的父亲一定会选择他来提高平均值,我们维护这个过程,每次找出最大值,记录答案后将他和父亲合并,循环此过程即可。
全局最值用优先队列,合并操作用并查集维护即可。
9.27
ema
构造题也给我滚出oi !!!😡😡😡
奇数的构造是简单的,发现当一个节点确定时,和其他每个节点的和在 \(\mod n\) 意义下不同,所以直接那这个当边的颜色。直接用 \(n\) 种颜色就行了。
考虑偶数的构造,去掉一个点后直接应用奇数的策略,最后剩一个点,发现之前的每个点都剩了一种颜色,连上就行了。
meruru
回来吧我的三维偏序,我最骄傲的信仰☺️☺️☺️
直接做不好做,单步容斥,转成求无交矩形数,分为八种,直接减去上下左右的(这一部分用树状数组维护一下就行),发现左上,左下,右上,右下被多算了,用树套树暴力二维数点一下就行,轻松过原题。
唉不是,你怎么卡我树套树啊😓😓😓,把二维数点用 CDQ 做就过了。
整数的lqp拆分
没有生成函数的生成函数题。
设 \(f\) 为斐波那契数列, \(g\) 为答案,很容易得到 $g_{i+j}=\sum (f_i \times g_j ) $,意义是在和为 \(j\) 是选择了 \(i\) 这个数,从而乘上了 \(f_i\) 的贡献。
把这个式子写出两项。
\(g_i=\sum f_{i-j} g_j\)
\(g_{i+1}=\sum f_{i-j+1} g_j\)
上下做差,得到 \(g_{n}=2 g_{n-1}+g_{n-2}\)
这不是我们矩阵加速板子吗?🤣🤣🤣
9.28
棘手的操作
操作好多,但是好板。
看到连边操作,自然想到线段树合并,发现线段树合并+并查集维护集合能解决除了全局操作的所有操作,太强了。
考虑如何维护全操作,我们可以用一个 set 维护每个连通块的最大值组成的集合,查询时取最大即可,修改操作在线段树操作后更新一下就可以了。
忽略并查集复杂度 时间复杂度 \(O(n\log n)\),唉不是这个 set 常数好大啊。不是题解区怎么都是可并堆和离线。
小众。
9.29
捉迷藏
线段树+性质题,一个经典的性质是在树上合并两个点集的直径时,新的直径的端点一定来自于原来的四个端点。
有了这个性质就很好做题了,用线段树维护点集合并的过程,黑点的初值为 \((x,x)\) 即自己到自己,白点的初值为 \((0,0)\) ,代表不可作为端点,单点求和,全局查询。
时间复杂度 \(O(n\log^2 n)\),除去线段树的 $\log $,还有一个在合并时算距离 lca 的 \(\log\) ,可以用 \(O(1)\) lca 消去,但没必要。
下午 ZJCPC 耻辱五题下播。
遥远的国度
LCT 可做 ? 其实是简单树剖分讨题,
考虑当前根 \(rt\) 与 \(x\) 相对位置的情况,可分为三种
- \(rt=x\) 直接查全局答案。
- \(lca(rt,x) \neq x\) 对 \(x\) 没有影响,正常查子树。
- \(lca(rt,x)=x\) 只有 \(rt\) 所在的 \(x\) 的儿子的那棵子树不能查,扣掉那段
dfn序后查剩下的就行了。
9.30
特别行动队
把 \(X\) 拆成 \(s_i-s_j\),就是一个经典的斜率优化式子,直接上李超树就行了。
Sequence
先对于每个 \(a_i\) 减 \(i\),将问题转化为最长不下降序列,限制就变得宽松了。
每个区间想要代价最小,一个显然的想法是取中位数。
我们对于每个数,如果合并之前就满足不下降,就保持不变。否则就与后面的合并来保持不下降的限制。
最后的答案就是中位数加上 \(i\) 后的结果。
小卡与落叶
线段树合并裸题,动态开点线段树的下标记录深度,记录下每个询问时,黄树叶的深度,每次区间求和即可。
10.2
模拟赛,写一下题解
T1
经典 trick ,把大于 \(X\) 的数赋 \(1\),其他赋 \(0\)。区间排序就变成了区间求和和推平。
线段树维护即可。将大于改为大于等于,变化的位置就是 \(X\) 的位置。
T3
发现禁止出现的子串的长度很短,只有 \(6\) ,所以维护字符串的后 \(5\) 位,由于字符集大小只有 \(2\),所以直接状压就行,再用矩阵加速一下,随便跑。
T4
最值分治板子,消去了最值的影响,变成了一个求区间小于/大于某个数的个数,传统二维数点的常数有点大,发现前缀和据有单调性,直接二分就可以了。
10.3
模拟赛,写一下会的题
T1
看到限制次数中有一个 \(\log\) 就要往带 \(\log\) 的算法上想,考虑分治,我们更希望每个元素被操作时位置比较靠后,所以优先给右区间赋值,再给第一个赋值,将整个区间翻转,左区间就到了靠后的位置,分治处理即可,注意区间是否处于翻转状态。求权值的代码照着 check 贺就行了。
10.4
忘情
式子很好化,直接把 \(\overline{x^2}\) 放进括号里,就是 \({((\sum x) + \overline{x})^2}\)
这是一个很版的斜优DP了,再用 wqs 二分消去个数的限制就可以了。
Strange Alice Game
简单数据结构题,先对值域扫描线,每次加入当前扫到的值,再查询前面第 \(k\) 个数,就是从哪里开始刷可以刷这个怪。
接下来就是简单扫描线,把询问挂在右端点,每次查询有多少个其实位置,小于等于左端点就行了。
时间复杂度 \(O(n\log n)\) 。
动态DP
用广义矩阵乘法,把转移式写成只和重儿子有关的形式,剩下的写进矩阵,每次修改时,修改这个点向上的每条重链即可,查询时查询根所在重链。
可以每条重链都开一颗线段树,常数更小,可以卡过加强版。
10.8
刚回来就考试。
T1
首先显然每只变色龙都至少要吃一个球。
注意到如果将一只变色龙吃下的所有球同时反转颜色,这只变色龙的最终颜色也会跟着反转。所以每只变色龙最后是红色的概率都是 \(\frac{1}{2}\),答案即为总方案数除以 \(2^k\),算总方案数只需简单容斥即可。
T2
要让没有重点的最短链最长,当固定根时,让每条链都从叶子开始向上延申,有冲突就让最短的先延申,这样的策略是最优的,答案就是到根的那条链或某个子树内的次短链。
维护 \(f_u\) 表示 \(u\) 子树内所有链的长度。
\(S\) 为所有次短链的长度。
换根时更新两个集合即可。
T3
中间忘了
T4
经典 \(trick\) 挂虚点求直径,对每个 \(u\) 建一个虚点,相连的边权为第一颗树上 \(u\) 到根的权值和,因为直径一定在虚点上,所以可以直接看作加了一个点权,用线段树维护出树的直径,离某个点最远的点一定在直径端点上。
换根时,用树状数组做一个子树加就可以了。
10.9
哈希真可爱
哈希求回文
正反串分别哈希一下,比较对应段哈希即可。
哈希求SA
快速排序,cmp 里写一个二分哈希求 lcp ,直接比较 lcp 的后一位即可。
时间复杂度 \(O(n\log ^2 n)\)
哈希求循环节
质因数分解
Hash killer
挑战 npc,勇夺图灵奖。
10.10
模拟赛
CF1830E(*3500)
这里有一堆结论,最后 操作数等于
动态逆序对维护一下就好了。
CF1004F(*2600)
单次询问是可以双指针直接做的,有修改,就把双指针搬到 push_up 里,然后经典结论,答案只会改变 \(\log\) 次,存一下转移点即可。
CF1572D(*2800)
显然是一个二分图,两个点如果有边,边权为两者权值之和,费用流跑最大权匹配就行了,发现时间复杂度错完了,因为只取 \(k\) 次,所以 只用至多 \(k\times 2n\) 条边就够了,取权值大的那些边,直接mcmf 就行了。
CF997E(*3000)
不用吉司机的做法。
这个东西一看就很序列分治,我们挂完询问后考虑答案的贡献形式。
类似于连号区间数,当 \(max,min\) 在同一边时,可以直接计算出满足要求位置,判断是否合法即可。
否则就要给 满足类似 \(max+l=min+r\) 的位置加上贡献。
普通的线段树很好维护第一种形式,却不怎么容易维护第二种。
再次深挖性质,发现 \(r-l\le max-min\),也就是说等式成立时 \(min+r\) 应取最大值,所以在线段树里再记录一下区间最大值和最值个数,就能实现给区间内的最大值加上贡献。
我们就可以完成本题的贡献计算。
回答询问就是简单区间求和。
具体地讲,对于计算跨 \(mid\) 的贡献。
我们枚举一个 \(i\),用双指针维护 \(j\) ,代表 满足\([i,j]\) 的 \(max,min\) 都在 \([i,mid]\) 的最小右端点,\(k\) 代表 \(max\) 在 \([i,mid]\) 的最大右端点。
我们分别对 \([mid+1,j]\) , \((j,k]\) 采用上述的贡献计算方法,用线段树维护每个位置的贡献,遇到询问就查询询问区间内的贡献和。
挂询问类似于 [NOIP2022] 比赛 和 序列。
10.12
品酒大会
根据 height 数组的性质, \(lcp(sa_i,sa_j)=min_{k=i+1}^j height_k\)
问题一就变成了求最小值大于等于某个值的区间个数,先转化成等于,在题个前缀和。
等于时可以用最值分治解决。
考虑第二问,发现在分治的同时选中点两端的点作端点一定满足 r相似,所以求出两边权值相乘的最大值记录即可(因为有负数,所以还需要考虑两个负数相乘)。
注意一下边界,这题就做完了。
10.13
模拟赛
T1
设 \(T\) 为最终的字符串,\(n\) 为字符串 \(S\) 的长度。为了方便计数,我们钦定 \(S\) 的每一位尽可能向 \(T\) 的后面匹配。对于匹配到的每一位,我们分开考虑,假设当前 \(S\) 的第一位匹配到了 \(T\) 的第 \(i\) 位。
对于第 \(i\) 位之前,显然可以任意填,方案为 \(26^{i-1}\);对于 \(S\) 的剩下 \(n-1\) 位,方案数为 \(C_{n+k-i}^{n-1}\);对于最后匹配的位置,由于我们钦定每一位尽可能向后面匹配,所以剩下没填的位置都不能和它上一个填过的位置重合,方案数为 \(25^{k-i+1}\)。最终答案即为:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=998244353;
const int N=2e6+10;
int inv[N],fac[N],k,n,ans;
char s[N];
inline int ksm(int a,int b) {
int res=1;
while(b) {
if(b&1) res=res*a%mod;
b>>=1;a=a*a%mod;
}
return res;
}
inline void init() {
n=strlen(s+1);fac[0]=inv[0]=1;
for(int i=1;i<N;++i) fac[i]=i*fac[i-1]%mod;
inv[N-1]=ksm(fac[N-1],mod-2);
for(int i=N-2;i>=1;--i) inv[i]=inv[i+1]*(i+1)%mod;
}
inline int C(int a,int b) {return fac[a]*inv[b]%mod*inv[a-b]%mod;}
signed main() {
scanf("%lld %s",&k,s+1);init();
for(int i=1;i<=k+1;++i) ans+=ksm(26,i-1)*C(n+k-i,n-1)%mod*ksm(25,k-i+1)%mod,ans%=mod;
cout<<ans;
return 0;
}
T2
原 P11390。
子任务一
暴力模拟。
子任务二
这部分和正解没什么关系。
由于值域很小,所以我们可以发现:区间长度为 \(\frac{k(k-1)}{2}\)。
证明:由于只有 \(k\) 种数,每种数的出现次数不能相同,所以每个数出现次数总和为 \(1+2+...+k\),也就是 \(\frac{k(k-1)}{2}\)。双指针维护一下即可。
子任务三
这部分是启发正解的。
对于一个数 \(a_i\),我们设 \(pre_i\) 表示 \(a_i\) 上一次出现的位置,则区间 \((pre_i,i]\) 产生贡献,区间 \(+1\);区间 \((pre_{pre_i},pre_i]\) 贡献消失,区间 \(-1\)。显然可以线段树优化,将题目转化为区间加、查询全局非 \(0\) 个数。
子任务四
我们重新转化一下题意:
记 \(S_i\) 表示存在出现恰好 \(i\) 次的数的线段集合,我们要求的即为 |\(S_1 \cap S_2 \cap... \cap S_k|\)。
思考一下子任务三时我们的做法:区间加、区间查非 \(0\) 数的个数。可以发现这就是矩形面积并。此时题目就相当于求 \(k\) 个矩形面积并的交,容斥一下即可。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
inline int read() {
int s=0,x=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') x=-1;ch=getchar();}
while(isdigit(ch)) s=(s<<3)+(s<<1)+(ch^48),ch=getchar();
return s*x;
}
struct info{int mi,cnt;};
inline info operator +(info f,info g) {
info res={0,0};res.mi=min(f.mi,g.mi);
res.cnt+=(f.mi==res.mi)*f.cnt+(g.mi==res.mi)*g.cnt;
return res;
}
int n,a[N],k,lst[N],pre[N];
long long ans;
struct SEG{
info tr[N<<3];int tag[N<<3];
#define mid ((l+r)>>1)
#define ls p<<1
#define rs p<<1|1
inline void pushup(int p) {tr[p]=tr[ls]+tr[rs];}
inline void pd(int p,int k) {tr[p].mi+=k,tag[p]+=k;}
inline void pushdown(int p) {pd(ls,tag[p]),pd(rs,tag[p]),tag[p]=0;}
inline void build(int p,int l,int r) {
tag[p]=0;if(l==r) {tr[p]={0,1};return ;}
build(ls,l,mid);build(rs,mid+1,r);pushup(p);
}
inline void add(int p,int l,int r,int x,int y,int k) {
if(l>y||r<x) return ;
if(l>=x&&r<=y) {pd(p,k);return ;}
pushdown(p);add(ls,l,mid,x,y,k);add(rs,mid+1,r,x,y,k);pushup(p);
}
inline int qry() {return tr[1].mi?n:n-tr[1].cnt;}
}t;
int main() {
n=read();k=read();
for(int i=1;i<=n;++i) a[i]=read(),pre[i]=lst[a[i]],lst[a[i]]=i;
for(int j=1,op;j<(1<<k);++j) {
t.build(1,1,n);op=__builtin_popcount(j);
for(int i=1;i<=n;++i) {
int nw=i;
for(int p=0;p<k;++p) {
if(j&(1<<p)) {
t.add(1,1,n,pre[nw]+1,nw,1);
if(pre[nw]) t.add(1,1,n,pre[pre[nw]]+1,pre[nw],-1);
}
nw=pre[nw];if(!nw) break;
}
int lt=t.qry();
ans+=lt*(op&1?1:-1);
}
}
cout<<ans;
return 0;
}
T3
原 P4922。
其实一堆技能里面有一大半是迷惑你的。
瞬身闪避:不如大招。
燃起来了:不如一刀大斩。
劳大肘击:不如大招。
吓哭超影 \(3000\):表面上看有 \(10s\) 时停,实际上,它会清除持续伤害 \(buff\),牢布打一下就后退打不到了,留尼卡多利原地杵 \(10s\),有啥用?不如大招。
弑神登神:不如大招。
综上所述,只有一刀大斩、大招、通灵螃蟹有用。但此时攻击还是太多了,考虑继续简化。
我们考虑大招的位置,设此时持续伤害 \(buff\) 有 \(i\) 层,减速有 \(j\) 层。
1.大招与一刀大斩结合:
大招在前:\(i \times (j+5+1)+(i+1)(j+1)\)
大招在后:\((i+1)(j+1)+(i+1)(j+5+1)\)
显然大招在后伤害更高。
2.大招与通灵螃蟹结合:
大招在前:\(i \times(j+1+5)+i \times(j+2)\)
大招在后:\(i \times(j+2)+i \times(j+5+2)\)
显然也是大招在后伤害更高。
或者你也可以感性理解:另外两个技能会叠 \(buff\),打游戏平时不都是先叠 \(buff\) 再开大嘛。
这时候就有策略了,我们先使用一刀大斩和通灵螃蟹,再开大。
考虑 \(dp\)。设 \(f_{i,j}\) 表示使用了 \(i\) 次一刀大斩和 \(j\) 次通灵螃蟹的最大伤害,则有转移:
\(a\) 指一刀大斩伤害,\(c\) 指通灵螃蟹伤害,\(r\) 指持续伤害 \(buff\)。
此时时间 \(O(n^2)\),空间滚动数组一下即可 \(O(n)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e4+10;
int a,b,c,ans1,ans2=N,f[2][N],n,hp,atk,r;
signed main() {
cin>>n>>hp>>atk;atk/=10;
a=atk*8,c=atk*7;r=atk;
for(int i=0;i<=n;++i)
for(int j=0;j<=n-i;++j) {
b=atk*12+atk*i*(j+6);
if(i) f[i&1][j]=max(f[i&1][j],f[(i-1)&1][j]+a+r*(j+1)*(i-1));
if(j) f[i&1][j]=max(f[i&1][j],f[i&1][j-1]+c+r*i*j);
ans1=max(ans1,f[i&1][j]+(n-i-j)*b);
if(f[i&1][j]>=hp) ans2=min(ans2,i+j);
else if(f[i&1][j]+(n-i-j)*b>=hp) ans2=min(ans2,i+j+(hp-f[i&1][j]+b-1)/b);
}
ans1<hp?printf("%lld\nhks",ans1):printf("%lld\nLeiZeProMax Nb!!!",ans2);
return 0;
}
T4
原 P12336。
我知道你想骂我,但是你先别急。
看到根号,先考虑平方。此时式子转化为:
由于 \(a,b,c,d\) 均为正整数,所以有 \(a^2+b^2+c^2+d^2>d\),于是就有 \((a \oplus b \oplus c \oplus d)^2>d\)。
设 \(a \oplus b \oplus c \oplus d=d+x\),那么有
考虑如何构造一个 \(x\)。不难发现任何一个 \(x\) 都是可以构造的,设 \(y\) 为 \(x\) 二进制下最高位,则我们可以令 \(d\) 的前 \(y\) 位全为 \(0\),此时只需要构造 \(a \oplus b \oplus c=x\) 即可。
为了构造更方便,我们考虑让 \(x\) 尽可能小。\(0\) 肯定不行,所以我们让 \(x=1\),即 \(a \oplus b \oplus c=1\)(当然前提是 \(d\) \(mod\) \(2=0\)),此时上式可以变为:
此时我们可以表示出 \(d\):
考虑如何构造出 \(a \oplus b \oplus c=1\),为了接下来方便,我们先特判掉 \(a=1\) 的情况,这个可以直接暴力枚举。
那么此时有 \(a>1\) 且 \(a<b<c\)。
考虑异或的性质。显然 \(a,b,c\) 二进制下的最高位不能相同,且 \(b,c\) 最高位相同。
这个时候就需要我们和出题入脑电波交流一下了。我们设 \(k\) 为 \(a\) 在二进制下最高位,令 \(b=a+2^k\),可以发现,此时 \(a \oplus b=2^k+2^{k+1}\),我们再令 \(c=2^k+2^{k+1}+1\),此时我们可以惊奇的发现,\(a \oplus b \oplus c\) 居然成了 \(1\)!并且我们可以发现,这种构造显然满足 \(a<b<c<d\)。
但这就完了吗?不,此时我们发现,我们忽略了这种构造的前提条件:\(d\) \(mod\) \(2=0\),代入发现,当 \(a \equiv 0(mod\) \(2)\) 时,此时满足条件;但当 \(a \equiv 1(mod\) \(2)\) 时,并不满足条件。
考虑如何调整构造方案,此时我们再与出题入进行一波脑电波交流。
我们可以发现 \(a \equiv b(mod\) \(2)\),\(c \equiv 1(mod\) \(2)\),当 \(a \equiv 1(mod\) \(2)\) 时,我们将 \(b,c\) 都减 \(1\),此时可以发现:\(a \oplus (b-1) \oplus(c-1)\) 依然等于 \(1\)。此时我们可以发现,我们满足了 \(d\) \(mod\) \(2=0\) 的条件。
构造完毕。总结一下:
1.当 \(a=1\) 时,暴力或者随便找一组解。
2.当 \(a\) \(mod\) \(2=0\) 时,令 \(k\) 为 \(a\) 在二进制下最高位,则 \(b=a+2^k\),\(c=2^k+2^{k+1}+1,d=\frac{a^2+b^2+c^2-1}{2}\)。
3.当 \(a\) \(mod\) \(2=1\) 时,\(b=a+2^k-1\),\(c=2^k+2^{k+1}\)。
至于无解嘛,你可以发现不存在无解情况。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int a,b,c,d;
signed main() {
cin>>a;int k=__lg(a);
if(a==1) {cout<<"4 28 40\n";return 0;}
if(!(a&1)) b=a+(1ll<<k),c=(1ll<<k)+(1ll<<(k+1))+1,d=(a*a+b*b+c*c-1)/2;
else b=a+(1ll<<k)-1,c=(1ll<<k)+(1ll<<(k+1)),d=(a*a+b*b+c*c-1)/2;
cout<<b<<' '<<c<<' '<<d<<'\n';
return 0;
}
10.14
模拟赛
T1
发现相当于把 n 的质因数分为若干组,问方案数。
每一个质因数加入时,都有两种选择: 加入先前的某组,或新开一组。
所以我们的 状态只需要记录分了多少组
转移方程 :\(f_i=(cnt*i+1)*f_i+cnt*f_{i-1}\)
其中 \(cnt\) 为该质因子的次数。
T4
小清新人渣的本愿 Plus
多了区间推平和区间加,强制在线。
首先讲解部分分。
n,m,V 分别是序列长度,操作数,值域
sub1,2 \(n,m,V \le 1e3\)
暴力
sub3 \(V \le 64\)
把 bitset 开小的暴力
sub4 \(l=1,r=n\)
开一个 bitset 维护全局操作及答案。
sub 5,6 无修改
开 \(\frac{n}{B}^2\) 个 bitset 记录每个块之间的集合。提前预处理一下就好了。
sub 7 无操作 2
正常分块,推平时整块打标记,操作涉及到散块时,下传标记重构散块。查询时如果整块有标记,不再直接用或合并 bitset 而是单点修改即可。
伪正解
延续 sub7 的思路,多记录一个加法标记,注意标记的优先级即可
正解
把上面的操作放到底层分块线段树上,然后手写一个 bitset .
10.15
pairs
三种情况分别考虑
一维:直接双指针维护即可。
二维:转切比雪夫距离,将问题转换为一个二维数点问题,用扫描线做一下就行了。
三维:发现每维的的值域很少,考虑枚举一维,剩下的两维做二维的操作就行了。
鬼街
折半警报器。考虑每个警报器,当他到达 \(k\) 次时,一定存在一个其检测的房间次数 $\geq \frac{k}{w} $ ,其中 \(w\) 为质因子个数。所以在安装警报器时,我们将 \(\frac{k}{w}\) 作为阈值放进每个房间对应的优先队列里,当达到阈值时,就检查是否真的触发了警报,若没有,更新阈值后重新放回队列即可。
10.16
购票
转移方程题面里已经给了
显然可以斜率优化,拆一下式子。
我们令 \(y=f_v-p_v\times d_v -q_v , k=-d_u , x=p_v ,b=f_u\)
就把转移式写成了一个 \(y=\min{kx+b}\) 的形式,可以李超树简单维护。
但可转移的部分是一条链,我们可树剖套线段树套李超树,进行一个链上的函数求最值。
如果链头的父亲符合条件,就直接跳到链头的父亲,否则答案一定在重链的连续的 dfn 区间上,二分一下即可。
时间复杂度 \(O(n\log^3 n)\) 空间复杂度 \(O(n\log n)\)
常熟小,比大多数 \(\log^2 n\) 算法跑的都快,最优解第五页。
关于空间,按理来说,线段树套李超树空间复杂度应为 \(O(n \log^2 n)\) ,但是李超线段树并不是一个传统意义上的 leafy tree ,即他的信息并不只储存在叶子上,所以动态开点时可以每次只新建一个节点空间复杂度降到\(O(n)\) ,也因此时间上的 $\log $ 很难跑满,配合树剖的小常熟 \(\log\) 自然跑的飞快。
10.17
伊蕾娜
考虑在每个子树内考虑贡献,当存在 \(k\) 棵子树同构时,答案就会乘上一个 \(k!\) 的系数,发现以重心为根时,根最多被换一次(当有两个中心且两边相等时),所以找出重心后,统计每个树贡献即可,唉,我树哈希怎么被卡了。
欧冠决赛
考虑球迷和警察的博弈,警察一定会在球迷最短路某个上的节点进行拦截,这时球迷就会改走次短路,我们可以求出每个节点到 \(n\) 的最短路,这是最短路树经典问题,然后在每个节点判断警察在此是否拦截。
10.18
外星人
每个二维平面的点可以对应主对角线上的一段区间,问题就转化为用若干个区间覆盖给定的所有区间。花费为区间长度平房的和;
用 wqs 二分解决最多选多少的限制,剩下就是一个简单的斜优。
10.19
萌萌哒
考虑暴力,我们将相同的位置用并查集合并最后答案就是 \(9 \times 10^ \text{集合数}\) ,但这样复杂度是 \(nm\) 的,考虑优化,我们把序列分块,每个块维护一个左端点的集合代表元素,整块更新时直接更新标记,散块更新时下放标记,重构该块。最后扫一遍统计答案。
序列
整体可以分为三维分别维护,时间,位置,值域,存在一个查询区间排名的操作,而每次加操作对每个位置的时间轴相当于一个后缀加,所以我们选择用分块维护时间和值域,扫描线扫掉位置维,位置维上的区间加操作可以差分成两个单点加,单点加对时间维的影响又可以用分块维护。
剩下就是一个教主的魔法。
10.20
模拟赛 菜
10.21
模拟赛,菜死了
10.22
模拟赛,你妈。
10.23
模拟赛,666
10.24
模拟赛,资本你赢了。
10.25
写一下T4题解
从暴力开始考虑,对于一个确定的区间 \([l,r]\) 和 \(y\) ,\(A_x\) 和 \(A_z\) 一定会取到 \([l,y)\) 和 \((y,r]\) 的前/后缀最大值。
感性理解一下,\(y\) 确定时,\(A_x\) 和 \(A_z\) 越大,更容易满足 \(2 \times A_y \le A_x + A_z\) 的限制,同时会使价值更大,是一个双赢的局面。
处理一下区间前/后缀最大值,枚举 \(y\) ,即可做到 \(O(nq)\) 的暴力做法。
进一步思考,当答案最优时,\(A_x\) 或 \(A_z\) 一定会取到区间内的最大值(除非 \(y\) 不得不取到最大值),所以我们就可以确定三元组的一个端点。
我们先考虑 \(A_x\) 取到最大值的情况,设 \(m_0=x\) ,我们求出 \((pos,r]\) 的最大值的位置为 \(m_1\) ,那么所有在 \((m_0,m_1)\) 之间的 \(y\) 都是合法的,因为限制式子相当于让 \(A_y \le \frac{A_x+A_z}{2}\) ,现在 \(A_{m_0}\) 和 \(A_{m_1}\) 都大于等于 \(A_y\) ,所以他们的平均值也一定大于等于 \(A_y\) , 所以我们直接取 \((m_0,m_1)\) 中的 \(A\) 的最大值计算贡献即可。
接下来我们 \(y=m_1\) 的情况,\(x,y\) 已经确定,\(z\) 沿用暴力时的思路,取 \((m_1,r]\) 中的最大值 \(m_2\) 作为 \(z\) 判断是否合法。
如果合法,该答案即为最优答案,因为 \(m_0,m_1,m_2\) 分别是可取区间内的最大值的位置,不会再有合法区间内的其他值使答案更优。
如果不合法,我们继续向后重复这个过程,选定 \(m_2\) 为 \((m1,r]\) 区间内的最大值的位置,对这个子问题递归求解即可,直到满足要求或区间长度不足选出两个数。
\(m_0=z\) 的情况同理。
最后我们证明时间复杂度:
主要的复杂度分析在于递归的次数的数量级,我们考虑何时会进行下一层递归:
移项得
子问题的递归条件同理为
归纳总结可得递归条件为
显然最多递归 \(\log V\) 次,其中 \(V\) 为值域。
我们在这个过程中只用到了求区间最大值及其位置,可以用线段树平凡的维护,修改操作的区间加是平凡的。
所以复杂度为 \(O(q\log n \log V)\)
这道题的代码是比较好写的,是一道偏思维的 DS 题。

嘟嘟嘟
浙公网安备 33010602011771号