2023.5 做题记录
5.2
CF1797E Li Hua and Array
标签:数论,数据结构
来源:CF 杂题选做
还是挺有意思的数论与数据结构结合的题。
操作形如把 \(x\) 变成 \(\phi(x)\),把 \(\phi(x)\) 和 \(x\) 连一条边,容易发现是一棵树形结构,并且不难证明树高不超过 \(logV\)。
那么题意就转化为了给出序列表示树上若干点,区间跳父亲,查询区间深度和、区间lca。
区间跳父亲是再典不过的均摊删除操作了,维护剩下的值中的最大值,如果最大值都无法删除这个区间就不用遍历(维护和也可以)。唯一有点意思的是区间lca,这个东西也是可以在线段树上维护的,维护线段树节点表示的区间内的lca,合并的时候查询两个子节点lca更新,在这题中可以用 \(st\) 预处理的方式,也可以树剖,常数都非常小。
复杂度大概是 \(O(nlognloglogV+V)\) 或者 \(O(nlogn+VloglogV)\)的。
证明树高是log级别:
如果 \(x\) 是偶数,\(\phi(x)\leq \frac{x}{2}\)。
否则,如果 \(x\) 是奇质数,\(\phi(x)=x-1\),\(x-1\) 是偶数。
否则,\(\phi(x)\leq \frac{x}{min_d d|x}\leq \frac{x}{2}\)。
5.8
[APIO2017] 商旅
标签:01分数规划,floyd
来源:历年 APIO 选做
非常典的01分数规划。
题目所求可以看做一个最优比率生成环,即边有权 \(a,b\),最大化所选的环上边的 \(\frac{\sum a}{\sum b}\),这个问题是一个经典的01分数规划。
那么考虑二分比率 \(k\),表示每单位时间消耗的额外代价,接下来考虑判定。
因为商品购进和卖出的城市是任意的一对都可以,在原图上很难满足这个要求,所以考虑把图转化一下,新图是一个完全图,两点 \((x, y)\) 间的边是通过在 \(x\) 买进、\(y\) 卖出某个商品 \(t\) 获得的最大收益。
接下来还要考虑经过边的时间以及二分的比率,这个可以通过 floyd 预处理出两两之间最短路 \(g_{i,j}\),在新图中的边 \((x,y)\) 的边权上减去 \(k*g_{x,y}\)。
最后只需要在这个图上跑出来正环即可,跑自己到自己的最长路即可,也可以通过 floyd 简单解决。
整体复杂度 \(O(n^2K+n^3log|V|)\),轻松跑过。
[APIO2020] 交换城市
标签:kruskal重构树
来源:历年 APIO 选做
这题是求最小瓶颈路,显然kruskal,但是题目两个车不能相遇的限制很难处理,有一种方法是魔改kruskal重构树。
首先考虑合法条件,只要两点所在连通块存在一个度数 \(>=3\) 的节点或者存在一个环就是合法的(重边也可以),换而言之,两点所在的连通块不能是个链。
于是我们想要满足在kruskal重构树上跳到点权 \(=v\) 的祖先时所能到达的子树内的点都满足上面的条件,而不是简单的连通。
考虑魔改,对于每个连通块额外维护一个标记表示是否已经不是链了。
那么新加入的边需要分类讨论:
1 . 两端点已经连通,那么如果之前是链现在就不是链了。
2 . 两端点还没有连通,如果有一个端点所在连通块不是链,那么新连通块也不是链,否则看一下两端点是否都是所在连通块的链端点。
这样维护出来的kruskal重构树(或者说是森林)就满足上述条件了,每次询问查询两点是否在同一连通块以及他们的lca即可。
合并连通块需要启发式合并,复杂度视 \(n,m\) 同阶为 \(O(nlog n)\)。
[APIO2017] 斑斓之地
标签:平面图,数据结构
来源:历年 APIO 选做
挺有意思的ds题。
题目相当于求矩形内连通块个数,两个格子连通定义为两个格子有公共边。
把蛇经过的点称为黑点,其他的为白点,把任意两个相邻的白点间连一条边,得到的图一定是一个平面图,问题就变成了求平面图的连通块个数。
根据平面图欧拉定理,有 \(V-E+F=连通块数\),其中 \(V\) 是点数,\(E\) 是边数,\(F\) 是图内部分割的区域数,那么现在就需要求出来矩形内的 \(V,E,F\)。
\(V\) 和 \(E\) 是最简单的,\(V\) 用全部的减去黑点数,\(E\) 用总边数减去含有黑点的边数,都可以转成简单二维数点。
\(F\) 要分情况,最简单的是四个白点形成一个小正方形,这个也可以简单二维数点,还有一种情况是割出来整个蛇的形状,不过好在题目给出蛇的移动方式保证了黑点一定只会形成一个连通块,所以当矩形把整个蛇包含并且留了一圈空白,那么把 \(F\) 增加1就好。
因为是交互题,所以相当于强制在线,用主席树实现二维数点即可。
复杂度 \(O(nlog n)\)。
[APIO2022] 排列
标签:构造
来源:历年 APIO 选做
细节很多的构造题。
有一个显然的事实是 lis 满足"加法原理",即把两个从 1 开始的连续递增序列放在一起,前面的整体加一个极大值,那么最终 lis 数量是两个序列的 lis 数量之和,不难证明。
于是就有了第一种解法:一个连续递增序列可以贡献的非空 lis 数量是 \(2^|S|-1\),\(|S|\) 表示序列长度,那么我们要找到一个数量 \(n\) 满足 \(\sum_{i=1}^{n}2^|S_i|-1=k\),这样做极端情况大概需要1000个数字,可以拿到63分。
这样太浪费了,有一种更省数字的方式,考虑先构造一个连续递增序列,然后整体加上极大值,这序列已经可以贡献 \(2^|S|\) 个 lis(包含空),考虑在其中插入一个非常小的值,设他右边有 \(x\) 个数,那么他就会产生 \(2^x\) 个非空 lis。
于是把 \(k\) 二进制分解即可,对需要放置的位置搭建一个 lds,最坏情况是 \(k=2^{59}+2^{58}-1\),这个情况需要 59+58=117 个数字,可以拿到91分。
我自己想到的就这些,如果是考场上就可以直接跑路了。
正解很巧妙,发现 lds 中相邻的两个位置如果前面已经有两个放过的数字,那么可以巧妙的省掉一个,具体看下图:
(拜谢I_am_Accepted大佬的图)
于是从左到右枚举 lds,如果某个位置与下一个位置相邻且前面已经放过至少两个数,那么就在下一个位置放之前放过的数的第二小和第三小之间。
最坏情况是没有两个数相邻,其他情况不难证明一定小于等于位数的一半,所以需要的位数是 lis:60 lds:30,加起来刚好90。
单次交互时间复杂度看实现,\(O(logk)\) 到 \(O(log^2k)\),不过 \(O(logk)\) 太小了没必要精细实现。
[APIO2016] 最大差分
标签:交互
来源:历年 APIO 选做
简单交互题,因为和交互库变量冲突导致没有1遍AC(误)。
我自己先想的第二个包,虽然第一个包远比第二个包简单。
首先可以先问出来 \(a_1\) 和 \(a_n\) 的值,然后算出来平均差分 \(dt=\lceil \frac{a_n-a_1}{n-1} \rceil\),答案至少是这个。
接下来把值域划分为 \(\lceil \frac{a_n-a_1}{dt} \rceil\) 个块,每次只需要看相邻两块产生的贡献即可。
算一下,值域一定被划分了 \(n-1\) 个块,每个数都被至少分入一个块,加上开头的一次全局询问,总询问次数是 \(1+(n-1)\),总遍历数字个数是 \(2n\),加起来刚好 \(3n\),如果想再卡一下可以不考虑 \(a_1\) 和 \(a_n\) 分块,理论可以做到 \(3n-2\),(upd:确实可以)。
第一个包更简单,从 \(a_1\) 和 \(a_n\) 开始,每次把左指针+1,右指针-1,每次询问 MaxMin(a[p-1]+1, a[q+1]-1, &a[p], &a[q])
即可,询问次数刚好是 \(\frac{n+1}{2}\)。
5.14
CF1119F Niyaz and Small Degrees
=[APIO2021] 封闭道路
标签:DP,堆
来源:历年 APIO 选做
有一个非常简单的暴力 \(n^2\),对于每个 \(x\) 跑整棵树的DP,设 \(f_{i,0/1}\),表示 \(i\) 节点及其子树需要满足度数限制最小代价,其中 \(0/1\) 表示是否割掉 \(i\) 到它父亲 \(f_i\) 的边,孩子向父亲上传时,如果需要考虑度数限制,可以先默认都选 \(f_{son,0}\),开一个堆维护 \(f_{son,1}+e(i,son)\),取前若干个就好,直到满足限制或者负数取完。
枚举 \(x\) 是不可避免的,单次严格 \(log\) 也不好做,考虑均摊的东西。
\(x=0\) 的答案是固定的,我们考虑从小往大求解。
对于所有 \(deg_i\leq x\) 的点都是无意义的,相当于贡献0,把这些点直接删掉不会对答案产生影响,所以可以看做每次在森林中做DP。
考虑现在已经求完了 \(x-1\) 的答案,现在要求 \(x\),对于所有 \(deg_i=x\) 的点,我们需要执行删除操作,那么直接把所有和 \(i\) 相连的未被删除的点的堆中加入一个边权,可以理解为此时 \(i\) 的DP值无论如何都是0。
剩下的就和暴力差不多了,复杂度看着很假,实际上是 \(\sum_{i=0}^{n-1}\sum_{j=1}^n [d_j>i]=\sum_{i=1}^n d_i=O(n)\),带个堆的 \(log\),总复杂度 \(O(nlog n)\)。
不想改交互格式了,传统题格式提交。
5.15
[APIO2021] 雨林跳跃
标签:倍增,猜结论
来源:历年 APIO 选做
注意题目限制了高度是排列,所以不需要考虑去重等诸多麻烦的问题。
先考虑判定问题,不难发现只需要满足 \(max[B,C)<max[C,D]\),并且这个是充要条件,如果这个不满足那么一定会被 \([B,C)\) 中的某个树挡住,这个不难用ST表解决。
接下来都讨论有解的情况。
接下来考虑怎么做两个单点 \(x,y(x<y)\) 的计算,从 \(x\) 出发,考虑贪心地走两边可到达的最高的点,如果某一时刻较高的那个已经大于 \(h_y\) 了,不难发现此时一定是左边,那么之后一直专心向右走就好。
两边第一个比它大的点可以单调栈 \(O(n)\) 预处理,跳的过程不难发现分前后两部分,分别用一个倍增数组维护就可以做到单次 \(log\)。
接下来考虑终点唯一的情况,我们要寻找最优的起点。设 \(lb_y\) 是 \(y\) 左边第一个比 \(y\) 大的数,那么 \(lb_y\) 及以前的数都不是合法的起点,所以可以选的只有 \([max(lb_y+1, A), B]\) 这一段,不难想到选最大的数一定是一种最优起点,因为此后必须在 \([lb_y+1, D]\) 之间跳跃,最大的数可以涵盖其他的数的路线,产生交集前不难发现最大的数走的步数一定最少。
最后考虑正解,如果终点的可能性不多,那么我们可以枚举这些终点,单次 \(log\) 地算出答案取 min。
先求出来 \(mx=max[B, C)\),令 \(y=rb_mx\),\(rb\) 的定义类似 \(lb\),不难发现 \(y\) 是一个很优秀的解,除非 \([y+1, D]\) 有一个比 \(h_y\) 大的数满足一些特殊的条件不断走左边也许会比 \(y\) 优,其他情况 \(y\) 一定最优。
考虑求出来 \(p=lb_y\),\(k=rb_p\),下面都假设 \(p,k\) 存在且 \(k\leq D\)。
如果 \(p>A\) ,那么存在一种 \(k\) 比 \(y\) 优的解是 \([A, p-1]\) 中的某个点走 \(p\) 可以一步到位走到 \(k\),这时候不难发现最优路线一定存在经过 \(p\) 的某个,因为 \([A,p-1]\) 中的点想走到 \(k\),到终点前至少要走一个比 \(h_p\) 大的点,这时候可以从 \(p\) 简单一步走到这个点,所以一定是最优的。
其他情况,如果需要经过 \(p\),那么和上面一样是最优路线,如果不经过,那么 \(y\) 就是唯一最优选择,因为 \([p,y]\) 之间的所有数都 \(\leq h_p\),不可能选择了 \(p\) 后还能走到 \(y\),\(y\) 与其他数的最优性也不用多说。
那么只需要比较 \(y\) 和 \(k\) 两个点谁更优就好了,当然前提是存在。
时间复杂度单次询问 \(O(log)\)。
5.17
[APIO2019] 奇怪装置
标签:数论
来源:历年 APIO 选做
小杯猜结论题,结论手膜就膜出来了。
结论是每 \(\frac{AB}{\gcd(A,B+1)}\) 一个循环,循环内每个数对各不相同。
给出的区间如果有大于一个循环的,答案就是一个循环。
否则把区间两端点取模,如果 \(l>r\) 则变成 \([l, \frac{ab}{\gcd(a,b)}]\) 和 \([1, r]\) 两个区间。
求区间并即可,简单 sort 一下就完了,复杂度 \(O(nlogn)\)。
简单证一下结论:
循环内互不相等是显然的,现在只需要证明循环的长度。
如果想让两个时间 \(t1, t2\) 对应的数对相等,那么需要满足条件:\(t1+\lfloor\frac{t1}{B}\rfloor=t2+\lfloor\frac{t2}{B}\rfloor\mod A\) 和 $ t1=t2\mod B$
不妨设最小循环是 \(h\)。则 \(t2=t1+h\)。
第二个式子要求 \(h\) 是 \(B\) 的倍数,重点在第一个式子,不妨设 \(h=kB\)。
那么有 \(t1+\lfloor\frac{t1}{B}\rfloor=t1+kB+\lfloor\frac{t1}{B}\rfloor+k\)
于是有 \(kB+B=0 \mod (A)\)。
取最小值就有 \(k=\frac{A}{\gcd(A,B+1)}\)。
5.23
[JOISC 2023 Day1] Two Currencies
标签:莫队,分块,主席树
来源:JOISC2023
随便找的题,根号做法是简单的。
看到树、路径查询就想到树上莫队。
先把边转点,由于一条边上不一定只有一个收费站,于是原树上一条边要拆成一个很长的链,但是无伤大雅,总边数是 \(n+m\)。
接下来就是在树上跑莫队,题目要求的东西相当于把路径上的收费站按权值排序后从小到大能选银币就选,不能选的时候剩下的就只能选金币,这个东西很容易离散化后用一个 \(O(1)-O(\sqrt m)\) 的分块完成,因为一共有 \(O(q\sqrt{n+m})\) 次修改,\(O(q)\) 次查询,这个分块复杂度是最优的。
所以总复杂度是 \(O(n+m+q\sqrt{n+m})\) 的,轻松跑过。
存在一个 \(log\) 的在线做法,可以类似[SDOI2013] 森林的主席树和树上差分,不过懒得写了。
5.24
[JOISC 2023 Day1] Passport
标签:最短路,线段树优化建图
来源:JOISC2023
因为我搬到模拟赛了,所以题解写的比较详细。
subtask2 (\(n\leq 300\))
建立图论模型,因为有 \(L_i\leq i\leq R_i\) 的限制,所以拿走的悦刻五代可以到达的村庄的并集一定是一个连续的区间。
我们建出来 \(O(n^2)\) 个点,每个点都是形如 \((l,r)\) ,然后枚举 \(k(l\leq k\leq r)\),从 \((l,r)\) 向 \((min(l, L_k),max(r,R_k))\) 连一条边权为1的边。
对于询问 \(x_i\),跑 \((x_i,x_i)\) 到 \((1,n)\) 的最短路就是答案,求最短路可以用01bfs。
考虑建反图,跑 \((1,n)\) 到其他点的最短路,就可以 \(O(1)\) 回答。
复杂度 \(O(n^3)\)。
subtask3 (\(q=1,x_1=1\))
从1开始贪心找当前可以到达的区间内 \(R_i\) 最大 \(i\) 的增广即可。
和正解无关,所以只给了1分。
复杂度 \(O(nlogn)\) 或者 \(O(n)\) 。
subtask4 (\(n\leq2500\))
考虑优化刚才 \(O(n^3)\) 的最短路做法。
01bfs求这个图的最短路看起来没法继续优化,点数也不太好降低,所以从降低图的边数入手。
仍然考虑点表示 \((l,r)\) ,刚才的瓶颈在于我们要枚举区间内的某个点来看之后增广的情况,并且因为区间两边都有可能被增广所以看上去枚举很有必要,事实上我们可以通过加入一些边权为 0 的边来降低边数。
我们可以先只考虑一个方向的增广,也就是从 \((l,r)\) 向 \((l,max_{i=l}^{r}R_i)\) 和 \((min_{i=l}^rL_i, r)\) 连一条边权为 1 的边。
但是这样两边都可以增广的点被忽略掉了,这时我们考虑可以把区间收缩,也就是 \((l,r)\) 向 \((l+1, r)\) 和 \((l,r-1)\) 连一条边权为 0 的边。
这样可以不断收缩到那个可以让两边都增广的点,然后连一条 \((i,i)\) 到 \((L_i, R_i)\) 的边权为 1 的边就好了。
这样每个点最多只会连出来 5 条边,所以图的边数是 \(O(n^2)\) 级别的。
建反图,跑从 \((1,n)\) 出发的最短路就是答案。
复杂度 \(O(n^2)\)。
subtask5 (\(q=1\))
只有一组询问,复杂度肯定不能是 \(n^2\),但是我们前面的做法光点数就是 \(n^2\) 级别的了,所以要考虑优化点数。
实际上,不需要用区间表示状态,观察到只需要能从 \(x_1\) 走到村庄 1 和村庄 \(n\) 就能完成游行,这是还是因为有 \(L_i\leq i\leq R_i\) 这个限制,所以我们可以考虑只需要 \(n\) 个点表示每座村庄,\(i\) 向 \([L_i,R_i]\) 内的每座村庄连一条边权为 1 的边,但是我们要求的答案是什么呢?
注意到最终的路径形态一定形如 \(x_i\) 先到某个中转点 \(d\),然后从 \(d\) 到 1 和从 \(d\) 到 \(n\)。
建反图跑从 1 和 \(n\) 开始的最短路,记作 \(a_i,b_i\),然后在正图跑从 \(x_1\) 开始的最短路,记作 \(c_i\),求出来 \(a_i+b_i+c_i\) 的最小值就是答案。
用线段树优化建图+01bfs就可以做到 \(O(nlogn)\),注意中转点被算了两遍,答案需要额外-1。
subtask6 (无特殊限制)
现在有多组询问,肯定不能每次跑一遍最短路了,但是反图上从 1 和 \(n\) 开始的最短路还是可以跑的,可以预处理然后记录下来。
其实没必要每次都在正图上从 \(x_i\) 出发跑一遍最短路,还是建出来反图,考虑我们新建一个超级点 \(n+1\),从 \(n+1\) 向每个点 \(i\) 连一条边权为 \(a_i+b_i\) 的边,跑一遍从 \(n+1\) 出发的最短路就好了。
这样预处理复杂度是 \(O(nlog^2n)\) 的,因为不能用01bfs最短路(也许可以?但是搬题人WA了),但是单词询问是 \(O(1)\) 的。
于是这题就做完了,时间复杂度 \(O(nlog^2n)\)
5.25
楼房重建
标签:线段树
来源:?
虽然是典题,但是一直咕着,今天写了。
显然把每个元素转成 \(\frac{H_i}{i}\) 后求从 \(1\) 开始的严格递增序列长度,用 \(O(logn)\) 的线段树上二分做 push_up 就可以维护区间的信息了。
具体的,因为有递增的元素就必须选,所以合并两个子区间的时候左儿子是必须继承的,左儿子的序列最终停止在左儿子区间的最大值上,那么之后就需要在右儿子通过线段树上二分找到第一个大于这个最大值的位置,加上他后面的序列即可。
复杂度是 \(O(nlog^2n)\)。
CF1679E Typical Party in Dorm
标签:状压,FWT
来源:?
与串串结合的简单状压题。
因为 \(n\) 不大,所以可以 \(O(n^2)\) 枚举每个区间,提取出这个区间成为回文串的条件(当然前提是已经填过的字符要对称),形如问号需要对称已经填过的字符用到的字符集 \(s_{l,r}\) 以及区间内和区间外加起来可以随便填的问号的数量 \(c_{l,r}\)。
暴力 \(2^17\) 枚举字符集 \(t\),看 \(s_{l,r}\) 时候是 \(t\) 的子集,如果是则可以给询问字符集是 \(t\) 时候的答案贡献 \(|t|^{c_{l,r}}\)。
这样做复杂度是 \(O(2^{17}\times n^2)\) 的,爆炸。
考虑用FWT减少枚举量,设 \(f_{s,i}\) 表示字符集为 \(s\),将来的字符集大小是 \(i\) 的贡献和,那么对于区间 \(s_{l,r}\) 直接枚举将来的字符集大小 \(j\) 直接给 \(f_{s_{l,r},j}\) 贡献 \(j^{c_{l,r}}\) 。
做完这个之后FWT一下求个子集和即可。
复杂度 \(O(17\times n^2+17\times 2^{17})\)。
5.28
[APIO2023] 赛博乐园
标签:分层图最短路
来源:APIO2023
简单题,场上一直T,赛后加个O2就过了,不懂。
考虑对每次操作除以2做一次分层,具体的,建出来 \(k+1\) 层图,第 \(i\in [0,k]\) 层的边权是原图的 \(\frac{1}{2^(n-i)}\) 。下面的 \((i,j)\) 表示第 \(i\) 层的点 \(j\) 。
对于可以进行除以2操作的点 \(u\),设它连出去的点是 \(v\),连 \((i,u)->(i-1,v)\),边权是 \(\frac{c}{2^{n-i+1}}\)。
不考虑其他的东西,跑 \((k,h)\) 到 \((i,0)\) 的最短路,取 min 就好。
对于其他的可以让时间变为0的点 \(x\),先判断 \(x\) 是否能与 \(0\) 不经过 \(h\) 连通,这个用并查集就可以了,如果连通就再和到 \((i,x)\) 的最短路取 min。
其他的就是注意除了 \((k,h)\),每一层的点 \(h\) 是不可以转移到其他点的,因为题面要求到 \(h\) 时立即停止。
注意到 \(\frac{1}{2^{80}}\) 足够小,所以让 \(k\) 和 80 取 min 就好。
没负权,跑dij就可以了,时间复杂度 \(O(nklog(nk))\),空间复杂度 \(O(nk)\)。
[APIO2023] 序列
标签:线段树,双指针,均摊分析
来源:APIO2023
很厉害的ds题。
考虑求一个区间的中位数的过程,我们采用这种方法:枚举答案是 \(x\),求出来小于 \(x\) 和大于 \(x\) 的数量差的绝对值 \(a\) 和 \(x\) 的出现次数 \(b\),如果 \(a\leq b\),那么 \(x\) 就是这个区间的中位数之一。
换句话说,我们把小于 \(x\) 的看做 -1,大于 \(x\) 的看做 1,剩下的 \(x\) 可以选填 \(-1,0,1\) 的任意一种,如果存在一种方案使得区间和为0,那么 \(x\) 就是这个区间的中位数之一。
考虑从小到大枚举 \(x\),这样可以做到均摊修改,现在考虑如何计算钦定中位数是 \(x\) 时的答案。
假定我们选中了 \(x\) 出现位置的第 \(l\) 个到第 \(r\) 个这段区间,考虑先把所有 \(x\) 出现的位置赋值为 -1,对于 \(L\leq l\leq r\leq R\),我们求出最小的区间和 \(sum(L,R)\),记作 \(v1\),然后把所有 \(x\) 出现的位置赋值为 1,类似的求出最大的区间和 \(v2\)。容易发现,区间和在 \([v1,v2]\) 的区间都是存在的,因为如果移动指针,可以让区间和+/-1,如果区间内包含 \(x\),同样也可以进行+/-1,所以取值是连续的,所以如果 \(v1\leq 0\leq v2\),那么一定存在某个区间 \(L,R(L\leq l\leq r\leq R)\),这个区间可以让 \(x\) 成为中位数,并且因为 \(L\leq l\leq r\leq R\) 的限制,答案一定是大于等于 \(r-l+1\) 的。
有了这个结论,我们可以做一个双指针,对于左端点,我们不断在满足 \(v1\leq 0\leq v2\) 的前提下增加右端点,容易证明 \(v1\) 单调不降,\(v2\) 单调不升,所以这个双指针是对的。
于是复杂度均摊修改用数据结构维护均摊 \(O(nlogn)\),双指针复杂度均摊 \(O(n)\)。