两三道 joi

发布时间 2023-11-10 11:43:36作者: Feynn

\(\color{violet}{\text{P7219[JOISC2020]星座3}}\)

笛卡尔树的题还是写得少了。建笛卡尔树,然后考虑从下向上合并答案。由于每行只会保留一颗星星,所以可以用 \(f_{x,y}\) 表示第 \(x\) 行保留了第 \(y\) 列的星星的最有答案。合并上是简单的,假设当前的限制是 \(h\),那么不大于 \(h\) 的可以贪心求解,并且只需要把它 ins 到 \(h\) 这个点上就可以了因为后面再不会用到了。后面就是一个区间加和线段树合并了,写法上由于如果一个子树是空的说明这个点尚且没有 DP 值,所以不需要在 psuhdown 的时候特殊处理。

\(\color{violet}{\text{P7212[JOISC2020]ジョイッターで友だちをつくろう}}\)

不太想说这个题。把双向关系看成一条双向边,那么一个双向边连成的连通块实际上是一个团,维护这样的团的集合即可,合并时启发式合并即可。但我不会实现调了一整天还没调过人麻了。

\(\color{violet}{\text{P7213[JOISC2020]最古の遺跡3}}\)

很强的 DP。你需要大力观察性质,从后向前考虑震柱子的过程,假设 \(t_x\) 表示某个柱子所有地震过后的高度,\(h_x\) 表示某个柱子的初始高度,那么有结论是说有最大的 \(r\) 满足 \([r,h_x]\subset\cup_{y>x}\{t_y\}\),那么 \(t_x=r-1\)。而一个柱子最后会留下来当且仅当比它小的高度不连续,这个留下了的柱子可能会导致本来不连续的区间变得连续,所以考虑以此为基础进行 DP。为了方便计算我们认为所有柱子不同,最后方案除以 \(2^n\) 即可。记 \(f_{x,y}\) 表示第 \(x\) 个柱子,对应后缀最大连续前缀是 \(y\) 的方案数,考虑当前柱子对第二维状态的影响。如果当前柱子被震垮了,那么这个柱子的高度只可能是不大于 \(y\) 的某个数,这样的柱子总共有 \(2y\) 个,而前面坚挺的柱子用的数量和倒塌的柱子(显然这些柱子也一定在这个集合之内)的数量是已知的,所以转移系数可以计算;如果当前柱子没有被震垮,考虑这个柱子往后的前缀大小,枚举这个大小 \(z\),思考转移系数。如果 \(y=z\),说明我们拿了个不知道多高的柱子,此时的方案数延后到后面计算;如果 \(y\ne z\),转移系数由三部分构成。一方面,当前柱子高度在 \([y,z]\) 之中,由于其中的长度一定恰好被用过一次所以系数好算;另一方面,我们需要 \([x+1,z]\) 高度的柱子,这些柱子在前面的某些位置,由于未确定的柱子数量容易计算这一部分的系数也是容易的;再一方面就是拿出这些柱子形成这样一个形式的方案数了,记为 \(g_x\)。相似地考虑如何转移 \(g\),考虑最后一个柱子的高度,假设为 \(h\),那么等价于分成了两个部分,下面部分的答案就是 \(g_{h-1}\),上面部分由于要凑出来一个合法的空中岛屿答案是 \(g_{x-h}\),相似地乘上长度方案和选择位置的方案就可以得到 \(g\) 了。至此三部分的系数都已经求出来了,整理一下就可以暴力地以 \(O(n^3)\) 的复杂度解决问题。

\(\color{violet}{\text{P9340[JOISC2023]Tourism}}\)

大力回滚莫队,答案显然是相邻 \(\text{dfn}\) 的点的距离之和的一半,瓶颈显然在于计算两个点的距离和找到一个点相邻 \(\text{dfn}\) 的那些点上,由于写的是暴力做法这两者都需要做到 \(O(1)\) 的复杂度。前者可以欧拉序并 RMQ 解决,后者可以一开始加入所有点,删除点的时候用链表维护相邻点的编号即可,调一下块长即可通过。

\(\color{violet}{\text{P7406[JOI2021Final]集合写真}}\)

分析一下发现题目中的限制等价于希望把原序列划分一下然后把每一段降序排列,于是用 \(f_{x}\) 表示已经规划好了前 \(x\) 个数所要的最少交换次数。切记交换次数只能由逆序对个数计算,不能用对应点的距离来算,后者应用场景相当窄。然后枚举左端点,贡献计算上考虑当前逆序对和之前的逆序对树状数组维护即可。

\(\color{violet}{\text{P7565[JOISC2021]ビーバーの会合2}}\)

显然只有点数为偶数时可能存在这样的点,出现这种情况时树的形态应该是一条链两端挂着两个大小一样的子树,所以只需要对于每个子树大小找到最长的合法链的长度即可。统计链考虑点分治,对于当前分治的子树找到特定子树大小下最大的深度的值,子树间合并是容易的。需要注意当前分治中心对答案产生的贡献,此时一端的子树大小是其它所有子树大小之和。另外就是统计 size 的时候忽略已经处理过的点貌似是正确的,分析一下发现如果这种情况出现说明祖先对应的大小是瓶颈,而根据淀粉质的性质可以搓出来一个方案使得以该祖先为端点,这样瓶颈只在另一个端点并且长度不劣,这是对正确性的一个粗略的证明。

\(\color{violet}{\text{P7560[JOISC2021Day1]フードコート}}\)

挺好的题。如果不存在离开事件,那么问题变成了找到最早的时间使得当前位置的人数不小于询问的人数,输出这个时间对应的颜色就可以了。有离开事件的版本考虑在前面的基础上进行修改,即思考如何找到人数使得这样做是正确的,发现找到当前位置被 pop 的人数即可,这个值等于进入的总人数减去当前人数。前者树状数组维护,后者要用线段树,需要支持区间加和区间取 \(\max\),每个节点维护两个标记 \(p,q\) 表示当前数是 \(\max(x+p,q)\) 即可。后面是容易的。

\(\color{violet}{\text{P9529[JOISC2022]一流团子师傅}}\)

魏老师说得好,随便找团子大概率是可以剔除的。复杂度未知但是可以通过。

\(\color{violet}{\text{P9523[JOISC2022]复制粘贴3}}\)

大力区间 DP,用 \(f_{l,r}\) 表示搞出来区间 \((l,r)\) 所需的最小代价,考虑决策。在前面补一个字符或者在后面补一个字符的决策是平凡的,考虑这个区间通过复制可以到达哪些区间,只考虑那些左端点相同的区间是正确的,枚举右端点。如果右端点可以粘贴得到贪心粘贴,否则只能苦哈哈的在后面添加一个字符。复杂度应该是 \(O(n^3)\) 的,大概是严格跑不满所以可以通过。

\(\color{violet}{\text{P7562[JOISC2021Day4]イベント巡り2}}\)

有启发意义。看到字典序最小想到按编号从小到大枚举每个区间,看强行放入这个区间之后答案是否合法。具体到这道题,发现可以倍增维护 \(p_{x,t}\) 表示以 \(x\) 为左端点放 \(2^t\) 个区间后右端点的最小值,于是可以在一只可爱 \(\log\) 的时间内求出一个区间的答案。考虑当前的情况,发现答案应该是一些散区间的答案之和,而强行放入的区间最多会剖出两个区间,用 set 维护区间简单决策即可。没更新 nowData 挂了一发呜呜呜。

\(\color{violet}{\text{P5100[JOI2017Final]足球}}\)

分层图。大概分成三层,第一层表示球在左右滚动,第二层表示球在上下滚动,第三层表示球被某个人带着,一二层向三层连边边权是到这个点最近的球员距离,可以 bfs 求出,最后跑一个最短路即可。

\(\color{violet}{\text{P9520[JOISC2022]监狱}}\)

需要注意到一个事实。对于两条相交的路径,完全可以等一条路走完之后再走另一条,也就是说如果存在合法方案,那么一定存在一种顺序使得按顺序走不会产生冲突,这一结论可以通过调整法得到。最后得到一个路径和端点在路径上的一些路径时间上的偏序关系,树剖并线段树优化建图即可。

\(\color{violet}{\text{P9530[JOISC2022]鱼2}}\)

大线段树题。考虑找出每个点能吃掉的区间 \([l_x,r_x]\),发现如果左端点固定了那么不同的右端点数量应该是 \(\log V\) 个,因为每次区间被阻碍说明后面存在一个比当前区间和要大的元素。所以可以用线段树来维护区间内合法的区间集合,由于我们只关心恰好等于总区间的元素数量,所以每个节点只维护集合 \(L,R\) 表示左端点和右端点为区间边界的区间数量和对应的点数,合并上就枚举每个区间暴力向两边拓展即可,这样做显然是正确的,修改显然查询就是线段树查询,输出直接输出最大的区间的点数即可,因为一定存在答案。

\(\color{violet}{\text{P9525[JOISC2022]团队竞技}}\)

贪心地取每个位置最大的人就可以了,但这可能不合法,因为最大值可能是同一个人。但事实上这个人不可能产生贡献,删掉他直到方案合法即可,用 set 维护。

\(\color{violet}{\text{P7405[JOI2021Final]雪玉}}\)

简单题。每个雪球拿到的雪在数轴上是一个区间,所以可以二分出左端点和右端点。以右端点举例,一个点可以被当前点覆盖当且仅当这个点比右边这个点更早地到达这个点,读入全部操作之后存储每次到达新的最左点或者最右点的位置和时间,询问的时候二分即可。复杂度有两只 \(\log\)

\(\color{violet}{\text{P9596[JOIOpen2018]冒泡排序2}}\)

首先你需要注意到,冒泡排序的轮数实际上等于贡献逆序对个数最大的右位置的贡献值,这显然是个下界,如何发现这是上界不是很清楚。然后考虑如何维护这个东西,和某个非常牛逼的贪心题一样,发现贡献点的移动使得答案不劣,具体到这道题而言就是一个点向右下角移动不会更劣,也就是贡献点一定是某个不知名的后缀 \(\min\)。直接维护是复杂的,而可能的贡献位置的值恰好就是 \(p-1-\text{mn}\),其它位置如此计算出来的答案比实际值要小,所以直接对这个值集取最大值就可以了。维护上大概是对于值域维护一棵线段树,应该会有个区间加单点修改整体查询之类的玩意。

\(\color{violet}{\text{P9522[JOISC2022]错误拼写}}\)

考虑 DP。对于两个位置 \(l<r\),y由于前者拿来比较的数更加靠后,所以如果要求前者更小,等价于第一个不等于 \(s_l\) 的位置 \(t\) 应该满足 \(s_t<s_l\),相似地如果前者更大,那么说明 \(s_t>s_l\)。如果相同就无所谓了。用 \(f_{x,c}\) 表示已经填了后缀 \(x\) 并未 \(s_x\) 填了 \(c\) 的方案数,枚举第一个不同的位置,假如为 \(y\),思考什么时候不合法。如果 \(s_y>s_x\),那么不应该存在一个形如 \(w_l<w_r,l\in[x,y),r\ge y\) 的限制,反之亦然,这个限制影响的是时间轴上的一个后缀,所以只需要每次删除给定区间内未被删除的点的贡献即可,并查集维护。由于是大小关系,所以用 \(h_c\) 表示转移到字符 \(c\) 的贡献之和,删除是容易的。为什么要用并查集呢,不是用个栈或者 vector 就可以了。

\(\color{violet}{\text{P6118[JOI2019Final]珍しい都市}}\)

脑子没转过来弯怎么办,启发如果对每个点计算和定点的贡献并且不好计算的话,计算定点对每个点的贡献可能是个好方法。

对于一个点而言,只有它到最远点的链上的节点可能产生贡献。而一个点的最远点是直径的两个端点中的一个,所以以这两个端点为基准分别计算每个点的答案取 \(\max\) 就可以了。转化后的问题变成了每个点到根都有一些点,希望找到该点的距离独特的那些点。一个点不合法意味着其距离不独特,考虑造成其不合法,也就是距离和它相同的点的位置。这个点只可能在当前点的子树内或者子树外。如果这个点在子树外的话,一定会导致在某个祖先处造成损失,所以只需要考虑第一种情况。考虑维护一个集合 \(s\) 表示在祖先处未被淘汰的点集,考虑如何向下转移。对于长儿子,把集合中不大于次长链的元素剔除就可以了;对于短孩子,把集合中不大于长儿子的元素剔除即可,此时还可以统计该点的答案。需要注意的是完成一个点的遍历之后不需要把剔除的元素再 push,否则复杂度是错误的。正确性考虑不 push 回去只可能在某个祖先的短儿子处造成问题,而如果存在一种方案使得长儿子中不合法,短儿子中也一定可以找到一种方案使之不合法,因为在短儿子处一定可以搓出来一个和长儿子一样的方案使得这个点不合法。颜色数量简单维护即可。

\(\color{violet}{\text{P6880[JOI2020Final]オリンピックバス}}\)

大概思路是反向后会影响正向最短路长度的边的数量不会太多,这些边单独拿出来跑,剩下的边混在一起跑就可以了,计算最短路时使用没有堆优化的 dij 以获得更优的复杂度。

\(\color{violet}{\text{P5103[JOI2016Final]断层}}\)

你需要冷静一下。正着不是很好做,考虑如何倒着做,即思考最后在表面的那些点一开始在哪个地层即可。斜线不好维护,考虑把点 \((x,y)\) 转化成 \((x+y,y-x)\),那么 \(d=1\) 的操作原来是说 \(x-y<t\) 的点 \(x\leftarrow x+l,y\leftarrow y+l\),也就是转化后的对于所有 \(y<t\) 的点让 \(x\leftarrow x+2l\)\(d=2\) 的操作是相似的,修改变成了对于 \(x\ge t\) 的那些点 \(y\leftarrow y+2l\)。整个过程中两个维度都是单调的,所以可以用树状数组上二分来维护,复杂度可以做到一只 \(\log\),但是两只 \(\log\) 实测也跑得比较快。感觉降蓝似乎也不过分。

\(\color{violet}{\text{P8162[JOI2022Final]让我们赢得选举}}\)

首先单纯贪心是错误的,需要复杂度更高的算法。枚举选择了 \(n\) 个人作为助手,那么粗糙地肯定选择 \(b\) 最小的那些城市找助手,但是这样可能是错误的,这样的错误发生当且仅当最优解中这个城市作为被演讲城市,也就是说忽略的城市的决策是固定的。于是用 \(f_{x,y}\) 表示按 \(b\) 排序后前 \(x\) 个城市出了 \(y\) 个助手的最小时间,后面的城市显然贪心一下就可以了。这个算法的复杂度是 \(O(n^3)\) 的,可以通过。题解区存在更优秀的算法。

\(\color{violet}{\text{P7669[JOI2018]Commuter Pass}}\)

假设已经选定了最短路,那么第二种情况下节省的路程一定是最短路上连续的一段,否则一定不优。考虑枚举这段的起点和终点,显然它们在最短路 DAG 上是祖孙关系,枚举一个端点另一个端点递推求最小即可。

\(\color{violet}{\text{P8163[JOI2022Final]铁路旅行2}}\)

倍增维护跳 \(2^k\) 步之后一个点能到的范围即可。注意 multiset 的用法。

\(\color{violet}{\text{P9521[JOISC2022]京都观光}}\)

感官上觉得不会是 DP,并且感官上来说用到的值应该比较少。于是分析一个位置何时能被计入答案。考虑有三行 \(x,y,z\) 和两列 \(l,r\),目前有三种决策,即从三个位置完成跃迁。它们的代价分别是:从 \(x\) 走,代价是 \(a_x(r-l)+(z-x)b_r\);从 \(y\) 走,代价是 \(a_y(r-l)+b_l(y-x)+b_r(z-y)\);从 \(z\) 走,代价则是 \(a_z(r-l)+(z-x)b_l\),最后的路径经过 \(y\) 当且仅当中间的决策最优,分别决定一下发现等价于 \(\dfrac{a_y-a_x}{y-x}<\dfrac{b_r-b_l}{r-l}<\dfrac{a_z-a_y}{z-y}\),也就是说只有下凸壳上的节点可能产生贡献。列也是相同的道理,而涉及到行与列的决策时用相同的方法分析一下发现取斜率较小的位置即可。复杂度线性。

\(\color{violet}{\text{P9197[JOIOpen2016]摩天大楼}}\)

去年的一个题。大概就是把差值之和差分一下,所以状态中只需要记录当前段数即可。然后就没了。

\(\color{violet}{\text{P9352[JOI2023Final]Cat Exercise}}\)

感觉没有那么显然。显然的是我太弱了。

考虑怎样的节点顺序是合法的,显然一个序列合法当且仅当序列单减,并且相邻点的路径上不存在比较小点更大的点。考虑令 \(f_{x}\) 表现从 \(x\) 出发可以做到的最长路径的长度,暴力转移即可做到 \(O(n^2)\) 的复杂度。然后需要观察性质,发现只需要记录连通块中 \(f\) 最大的点编号即可,原因在于这种策略不正确当且仅当存在两个点 \(x,y\),满足 \(f_{x}>f_y,f_{x}+\text{dis}_{u,x}<f_y+\text{dis}_{u,y}\),这是不可能的,因为这一旦发生意味着 \(y\) 可以更新 \(x\)\(f\) 值,前面的条件也就不满足了。所以从小到大维护连通块内 \(f\) 值最大的点即可,复杂度线性对数,瓶颈在于求两个点的距离。

\(\color{violet}{\text{P9351[JOI2023Final]Maze}}\)

适合不带脑子地锻炼手指。记 \(f_{x,y}\) 表示起点到这个点最少需要操作几次,发现整体 DP 值是呈层状分布的,层和层之间的转移可以 bfs 解决。答案就是终点的 \(f\) 值。没什么细节。

\(\color{violet}{\text{P9196[JOIOpen2016]销售基因链}}\)

正反串建 Trie,每个限制对于前后缀都相当于是限定 dfn 序在一个区间内,所以问题等价于二维数点。容易离线辅以树状数组解决。好像也可以 bitset 暴力艹过去。

\(\color{violet}{\text{P7563[JOISC2021Day4]最悪の記者4}}\)

线段树合并题目。这个故事告诉我们线段树合并时应该以点而非区间为基准,不然可能会比较麻烦。回到这道题。暴力的方法很显然,考虑用 \(f_{x,y}\) 表示点 \(x\) 的权值为 \(y\) 的最小代价,显然限制形成一个基环树,环上的权值要求相同,所以简单树形 DP 就可以了,复杂度 \(O(n^2)\)。考虑如何优化这一过程,发现可以用线段树合并来解决,并且由于一个点的有效权值实际上只可能是子树中出现过的权值或者极大值,所以一个点对应的线段树节点数量是有限的,所以线段树合并复杂度是正确的。具体地来说,合并的时候从右向左维护,同时维护两个 tag 分别表示 wh 和 th 线段树内后缀的最小值,原因在于转移的时候如果值是 wh 中的转移代价是 th 中一个后缀的最小值,如果值是 th 中的那么转移代价是把 wh 中某个较大的位置无缝拉下来,效果仍然是在取最小值。所以线段树每个节点需要维护区间加的 tag 和区间最小值的 tag。不存在太多下放的问题因为修改只会有单点修改。然后就是两个 tag 的初始化问题。然后就是环上合并的问题。多少有点抽象。

\(\color{violet}{\text{P9527[JOISC2022]洒水器}}\)

哇,太聪明了。直接做是不好做的,观察数据范围发现题目中的 \(k\) 很小,考虑从距离入手。对于一个贡献点 \((x,d)\),容易想到的是从 \(x\) 向上走 \(d\) 个点设计为询问点的 \(\text{lca}\),并打上一个标记 \(d'\) 表示如果离这个点的距离不超过 \(d'\) 的话可以被贡献。然而这样涉及到一些麻烦的去重,而且模数不是质数导致去重不太可行,再其次这样做复杂度带了两只 \(k\) 是不可接受的。然后就有一个很聪明的方法,是说假如两个点的距离是 \(s\),那么必然存在一个数值 \(k\) 使得 \(s-2k\) 恰好是 \(0\) 或者 \(-1\),而减去的过程相当于两个点向上跳的过程。所以对于这个东西维护即可,修改和查询只需要访问对应点对应的距离对应的数组就可以了。复杂度是 \(O(nk)\) 的。

\(\color{violet}{\text{P6117[JOI2019Final]コイン集め}}\)

拉回到上下两条水平线上后经典方法贪心即可。

\(\color{violet}{\text{P9329[JOISC2023Day1]Two Currencies}}\)

显然对于一个确定的路径应该贪心取需要银币最少的那些收费站。以银币数量为下标建立可持久化线段树查询时二分就可以了,复杂度 \(O(n\log n)\)

\(\color{violet}{\text{P7214[JOISC2020]治療計画}}\)

强。以时间为纵轴,村民为横轴画一张图,然后把每个治疗方案对应的村民涂黑可以得到一些等腰直角三角形,这些方案总是会随着时间的流逝而失效的,除非它们和其它方案产生了交流。具体地,假设有两个方案 \(t_i<t_j\),那么到时刻 \(t_j\) 时已有的村民区间是 \([l_i+\delta t,r_i-\delta t]\),我们希望两个区间有交也就是希望 \(l_i+\delta t\le r_j+1,r_i-\delta t+1\ge l_j\),不那么显然地等价于说 \(r_i-l_j+1\ge|t_i-t_j|\)。然后就有朴素的算法了,大概就是满足上面条件的点对连边,然后呢跑所有 \(l=1\)\(r=n\) 的点的最短路,这显然是正确的,然而复杂度过高。考虑拆绝对值,当 \(t_i>t_j\) 时有 \(r_i-t_i+1\ge l_j-t_j\),反之有 \(r_i+t_i+1\ge t_j+l_j\)。于是对于当前向外转移的点考虑哪些点可以转移,显然在前缀和后缀中找那些 \(l-t,l+t\) 最小的那些就可以了,由于图的性质每个点第一次被找到时就是最短路所以转移即可。复杂度一只 \(\log\)

\(\color{violet}{\text{P7407[JOI2021Final]ロボット}}\)

有三种决策。一种是选择一条边,然后把这条边颜色边集中其它边全部赋一个奇怪颜色,一种是把选择的这条边赋成一个奇怪颜色,第三种是说如果一个点存在一个入边和出边并且两条边颜色相同的话只需要一次操作就可以完成两条边的初始化,每个点对于每个颜色建立虚点连边即可。最后跑最短路以得到答案。