JOI2013-2020
代码自己去LOJ看
JOI2013
彩灯
把序列划分成若干极长交替列,那么最优的方案一定是将一个极长交替列翻转使得连续的三个极长交替列合成一个。计算相邻三个极长交替列长度的最大值即可。
搭乘IOI火车
DP:\(f_{i,j}\)表示两个串的起点分别在\(i,j\)位置最长的拼接长度,转移就先放一个'I',再放一个‘O’看能够转移到哪些位置。
现代豪宅
把起点、终点和所有有开关的点建两个点分别表示南北和东西方向,\(x\)相同、\(y\)相邻的两个点南北方向连距离的边,\(y\)相同、\(x\)相邻的两个点东西方向连距离的边,每个有开关的点南北方向和东西方向之间连1边表示切换方向,然后跑最短路。
JOIOI塔
从后往前考虑。O一定放在一个准备做基底的I上面,J一定放在一个'OI'上面,但对于I,既可以放在'OI'上面,又可以做基底。不难发现当基底需求量确定时,如果当前基底数量小于需求量,则放基底更优,否则放在'OI'上更优。于是二分基底需求量,也就是二分答案,然后跑一遍模拟一下上述操作看能否合成对应多个塔。
冒泡排序
首先不难注意到选择交换的左端点是前缀最大值、右端点是后缀最小值时更优。我们把这两个序列求出来,这两个序列都是单调递增的。
对于一个位置,我们考虑它能够产生的贡献。首先交换的左右端点得越过它。当左端点权值\(>\)当前点权值、右端点权值\(<\)当前点权值时,这个位置能够减少\(2\)的逆序对;当其中有一个相等时,可以减少\(1\)的逆序对;否则没有影响。注意到能够减少逆序对的左右端点在前缀最大值、后缀最小值数组上都对应一段区间,那么一个位置相当于矩形+一个数,然后求平面的最大值,用扫描线+线段树统计。
JOI2014
JOI徽章
枚举替换的位置和替换的字符,处理最多4个矩形对答案的影响。
IOI馒头
因为当总容量确定之后一定选价值最高的那些馒头,于是背包求出每种容量的最小代价即可。
年轮蛋糕
先断环成链,变成形如若干个将\([i,i+n-1]\)分成三段使得三段和最小值最大的问题。
注意到当询问由\([i,i+n-1]\)变到\([i+1,i+n]\)时,分成的三段的第一段的右端点一定不降。于是我们维护第一段的右端点,每一次check是否右移就二分第二段的右端点。
飞天鼠
当\(X=0\)时,最优策略一定是每一次爬到恰好能去到某棵树的高度然后飞过去,也就是到达每棵树时\(X\)都是\(0\)。先从\(N\)最短路跑出在\(X=0\)的情况下每个点到\(N\)的最短时间。
现在初始\(X\)不为\(0\),那么不难发现性质:在最优路径中一定存在一棵树满足从初始位置到达这棵树一直没有上升,从这棵树开始需要上升,然后基于\(X=0\)的策略行动。故从\(1\)再跑出从\(1\)开始在不上升的情况下到达每棵树时的最大的\(X\),最后枚举这样的树是哪棵就行了。
裁剪线
把平面看成无限大,四个边界也看成裁剪线。然后以\(y\)坐标扫描线,外部维护一个并查集,扫描线的过程中用一个数据结构维护以当前\(y\)值横截得到的所有段的左端点(最左边一段的左端点就是-INF)和这些段在并查集中对应的编号。一个与\(y\)轴平行的线加入就是把一个段分成两个,它们在并查集上对应的点相同;一个与\(y\)轴平行的线删除就是把两个段合成一个,并将它们在并查集上并在一起。
与\(x\)轴平行的线段加入相当于把这条线段覆盖的段在并查集上建立新点。显然暴力是不行的,考虑在数据结构上支持区间更新标记,并维护区间被更新但是没有在并查集上建立新点的段的数量。因为对于“被更新但是没有在并查集上建立新点的段”,它们没有在更新之后的任何操作中被用到,所以如果这个段再次被与\(x\)轴平行的线段覆盖,那么两次\(x\)轴覆盖之间的块一定单独成连通块。所以我们每一次在区间上打标记的时候,区间被更新但是没有在并查集上建立新点的段的数量也要贡献答案。
最后算一下并查集里面的集合数量,减掉最外围的无限大连通块就是答案。
注意当同一个纵坐标同时存在\(y\)轴加入、\(y\)轴删除、\(x\)轴加入时,处理顺序应该是\(y\)轴加入\(\rightarrow\)\(x\)轴加入\(\rightarrow\)\(y\)轴删除。
JOI2015
铁路旅行
差分求出每一条铁路的经过次数。
分蛋糕2
断环成链,设\(f_{i,j}\)表示IOI先手、蛋糕左端点为\(i\)、右端点为\(j\)时JOI能够拿到的最多的蛋糕,然后枚举JOI最开始选哪个蛋糕。
JOI公园
先跑出1到其他点的最短路。因为最优方案中\(X\)一定是某个点的最短路长度,于是从小到大枚举\(X\),维护两端最短路均\(\leq X\)的边的数量。
舞会
先二分答案,转化为序列中元素是\(01\)的问题。设\(f_i\)表示让第\(i\)个位置的人为\(1\)的最小代价,初始值当一个位置不确定时为\(1\),确定时如果当前位置为\(1\)则初值为\(0\),否则为\(INF\)。那么每一次从队头丢掉三个数、从队尾加入一个数,队尾加入的\(f\)值应该是队头丢掉的三个数的\(f\)值的较小两个的和。最后看一下队列中剩下的\(f\)是否小于等于当前没有确定的为\(1\)的人,就可以知道当前但是否合法。
城墙
设\(rd_{i,j}\)表示位置\((i,j)\)向右、向下的第一个障碍物到\((i,j)\)距离的较小值,\(lu_{i,j}\)表示位置\((i,j)\)向左、向上的第一个障碍物到\((i,j)\)距离的较小值。
那么对于一个满足条件的框的两个对角\((i,j)(i+p,j+p)\)需要满足\(rd_{i,j} \geq p+1 , lu_{i+p,j+p} \geq p + 1\)。也就是说一个点\((i,j)\)作为右下角可以匹配边长\(\leq lu_{i,j}\)的框,作为左上角则可匹配边长\(\leq rd_{i,j}\)的框。
枚举\(j-i\)的值,把所有位置拿出来一起算答案,从底往上用树状数组记录下能够与当前位置匹配的位置,每一次查询一段区间内的位置数量贡献答案。
JOI2016
橙子装箱
设\(f_i\)表示装前\(i\)个橙子的最小费用,从小到大枚举\(f_i\)向外的转移点维护\(a,b\)进行转移。
集邮比赛2
J放最前面、I放最后面最优,对于O计算出J的前缀和和I的后缀和,枚举位置可直接算答案。
铁路票价
先跑出最短路DAG,随便拿一棵最短路树出来,记下每个点的父亲。
一次修改如果改树边没有影响,如果改树边考虑在DAG上是否存在能够替换这条树边的边。如果有则替换并更新父亲,否则这条树边连接的最短路较长的点的最短路会增大。然后再递归处理最短路树上是这个节点的儿子的节点。
领地
先特判走一轮之后走回\((0,0)\)的情况。
假设走一轮之后JOI从\((0,0)\)到了\((px,py)\)。先通过将操作方向取反把\(px,py\)都弄成非负,然后对于一个被占领的正方形\((x,y)\)(这里是指\((x,y)\)是正方形的左下角),如果其四角第一次被经过时轮数最小值不是\(1\),那么\((x-px,y-py)\)也一定被占领,且其四角第一次被经过的轮数最小值减少\(1\)。即所有被占领的领地可以看作是至少一个角在第一轮时就被经过的被占领的正方形沿\((px,py)\)平移若干次经过的所有正方形的并。
先将所有点按序放在若干个集合里,一个集合里的所有点满足任意一个点都能通过向量\((px,py)\)和其相反向量平移若干次到达其他所有点。对于点对\((x,y)\)可以这样转换:当\(px = 0\)或\(py= 0\)的时候用增量为\(0\)的坐标和另一维坐标模它的对应增量构成点对进行分离;否则可以用点对\((x \mod px , x \times py - y \times px)\)进行分离。不难发现两个点对在同一个集合内当且仅当转换后点对相同。
接着枚举所有满足存在一个点最小经过轮数为1的正方形,算出其四角的轮数最大值,就可以知道它会沿着\((px,py)\)平移多少次。把所有矩形的次数算出来,但是有一些正方形平移之后会和之前的正方形重叠一部分。我们再把所有正方形按照给点分集合的方式分成若干可能会导致重叠的集合,对于每一个集合跑一遍区间覆盖就好了。
断层
题目意思是:在一个无限大的平面内,初始每个点的权值是其纵坐标。有\(Q\)次操作,每次操作是把所有满足\(x+y \geq X_i\)或者\(x-y \leq X_i\)的点沿着直线向上平移\(L_i\),问最后\((0.5,0)(1.5,0)...(N-0.5,0)\)这些点的权值。
把操作过程反过来,也就是最开始有\(N\)个点\((0.5,0)(1.5,0)...(N-0.5,0)\),每次操作把满足\(x+y \geq X_i\)或者\(x-y \leq X_i\)的点沿着直线下陷\(L_i\),问最后每个点的纵坐标。不难发现在任意一次操作后,所有点横坐标大小关系不改变、横纵坐标和大小关系不改变、横纵坐标差大小关系不改变,也就是说每次下陷的一定是一段前缀或后缀。那么需要数据结构支持前缀减、后缀加、得到第一个满足权值大于/小于某个给定值的位置,线段树可以胜任。
JOI2017
焚风现象
维护差分数组。
准高速列车
对每个高速列车站维护当前在这个高速站之后、下个高速站之前第一个到不了的城市和如果这里放一个准高速列车站会使得多少个站点被额外经过,然后贪心,每一次选择过后更新一下两个值。
JOIOI王国
整个图的四个角一定不会同时被同一个省占领。假设JOI省占领了权值最小的点,先枚举JOI省占领了哪个角落,然后从大到小枚举JOI省占领的最大的点,每一次枚举之后把这个点丢到IOI省去,更新IOI省占领的土地。用二分进行更新不难做到\(O(n^2logn)\)。
足球
分析性质,不难发现这些性质:
1、一个人将球传出去之后再控球一定不优,因为此时传球变得没有必要。这意味着我们不需要记录每个人在任何时刻的状态,只要记录每个人有没有控过球就行了。但这样似乎仍然很不好做。
2、假想在某个人控球之后,其原来所在的位置仍有一个球员,那么在这个人控球后也不会将球传给其原来所在的位置的球员,因为可以证明这样的方案一定不如“在这个人之前控球的人将球传到这个人所在的位置”优。这样就算一个人控过球也可以认为原来位置有一个球员。
这样就完全无需记录球员相关信息,只需记录球是否被球员控住。跑bfs求出每个点到最近球员的距离\(dis\),然后对于每个位置建三个点表示球在当前位置,有球员控球/没球员控球,在往东西方向滚/没球员控球,在往南北方向滚。从控球到滚的代价是\(B\),从滚到控球的代价是\(dis \times C\),滚和控球往相邻方向的代价就是\(A\)和\(C\)。然后跑最短路。
绳
JOI2018
寒冬暖炉
题目相当于用\(K\)个区间覆盖\(N\)个点使得区间总长最小。这等价于先用1个区间覆盖\(N\)个点,然后挖掉\(K-1\)个两个相邻的点之间的区间。挖掉的区间长度越长越好,于是求出相邻两个位置的距离然后取前\(K-1\)大。
美术展览
按照美观度排序之后一次选择一定选一段区间。枚举这个区间的左端点,那么之后每幅画的贡献就是“它的权值-(它的美观度-它前面的画的美观度)”,维护最大前缀和即可。
团子制作
有可能互相影响的串会满足其中的G团子的横纵坐标之和相同。
枚举这个和,把所有满足的G团子拿出来DP:\(f_{i,j}\)表示确定了前\(i\)个团子,第\(i\)个团子的串是横/纵的最大串数。转移枚举第\(i\)个团子的串的方向,那么与其恰好相邻的G团子的方向必须与它一致。
月票购买
先跑若干遍最短路跑出U到每个点的最短路\(disu_x\)、每个点到\(V\)的最短路\(disv_x\)、\(S\)到\(T\)的最短路DAG,然后在DAG上拓扑求出每个点的拓扑后继到达\(T\)的最短路\(f_x\),然后将\(disu_x+f_x\)取min就是答案。
毒蛇越狱
一个显然做法是枚举?是什么,复杂度\(O(2^{cnt?})\)。只用这种做法太low了,我们考虑拼上一些其他做法。
考虑高维前缀和,不难发现我们可以把?看作\(1\),对原序列跑一边高维前缀和,然后通过容斥把原序列中的\(1\)算多的部分减掉。这里的复杂度是\(O(2^{cnt1})\)的。
不难发现还可以把所有\(01\)翻转做高维前缀和,复杂度是\(O(2^{cnt0})\)的。
\(cnt0 , cnt1 , cnt?\)三者的最小值是\(\lfloor \frac{L}{3} \rfloor\),所以预处理两个高维前缀和可以将复杂度做到\(O(2^LL+2^{\lfloor \frac{L}{3} \rfloor}Q)\)。
JOI2019
勇者比太郎
算一下I和O的前缀和。
画展
如果确定了画框的数量,那么画框一定是从大到小选最优。于是从大到小给每个画框选择能够选的美观度最大的画。
有趣的家庭菜园3
DP:设\(f_{i,j,k,0/1/2}\)表示已经确定了序列的前\(i\)个位置、红色放了\(j\)个、绿色放了\(k\)个、最后一个元素的颜色是红/绿/黄时的最小步数,因为对于每种颜色,选择的元素是一段前缀,所以不难算出往后加一个元素时移动的代价。
硬币收藏
首先把所有的硬币移到它们在\(x \in [1,N] , y \in [1,2]\)中距离它们最近的位置并计算距离。
然后从左往右考虑列,将硬币和空位匹配。经过分析发现:若考虑到第\(i\)列,第\(i\)列上有额外硬币,在之前列上有空位,那么无论硬币和空位的纵坐标如何,当前硬币补充到之前空位上不劣。
如果空位和硬币在同一行显然补上去更优;否则假设空位由另一行更靠后的硬币填补,当前硬币填补本行靠后的空位,可以发现这样匹配横坐标方向额外经过的长度至少为\(2\),而在之前的填补方式中横坐标没有额外经过长度,纵坐标额外经过的长度为\(2\),所以选择之前的填补方式不会更劣。
那么按照这样的方式进行匹配,尽量同行和同行进行匹配,如果不能同行匹配但仍有额外硬币和空位则跨行匹配,算出贡献即可。
独特的城市
把直径拿出来,设两个端点为\(S\)和\(T\)。那么对于一个点,如果其离\(S\)更近,则不难证明对它来说独特的城市只能是它到\(T\)的路径上的点。
于是分别从\(S,T\)开始dfs,用一个栈维护\(S,T\)到当前点的路径上合法的独特点,然后用一个桶记录一下栈里面每种特产的出现次数。每次向下dfs的时候,先dfs重儿子再dfs轻儿子,每次把栈里面不合法的独特点从栈里面弹掉,把当前点入栈,然后往下dfs。不难发现在这样的dfs顺序下当一个点从栈里面被弹掉之后,在之后的过程中也一定没有用了,所以不会有额外的入栈操作,每一个点只会入栈它的儿子那么多次,总入栈次数就是\(O(n)\)的。所以复杂度是\(O(n)\)的。
JOI2020
只不过是长的领带
显然的贪心是删掉某一个之后将两个序列排序一一匹配。所以排序之后维护\(A\)序列的一段前缀匹配\(B\)序列长度相等的前缀的奇怪度和\(A\)序列的一段后缀匹配\(B\)序列长度相等的后缀的奇怪度就可以快速得到删掉每一个位置之后的奇怪度。
JJOOII 2
设左端点为\(p\)的包含"J...JO...OI...I"、"O...OI...I"、"I...I"(省略号表示对应字符有\(K\)个)作为子序列的字符串的最小右端点为\(f_p,g_p,h_p\),按照\(h,g,f\)顺序转移,转移时从后往前扫,用些东西维护当前位置向后遇到的第\(K\)个'J'、'O'、'I'的位置。
集邮比赛 3
暴力DP:设\(f_{i,j,k,0/1}\)表示在起点的逆时针方向走过\(i\)个收集点、顺时针方向走过\(j\)个收集点、经过\(k\)个时刻、现在在起点逆时针方向的第\(i\)个收集点或顺时针方向的第\(j\)个收集点,这样的状态下最多能收集多少邮票。转移考虑走到起点顺时针或逆时针方向上的下一个收集点。
\(k\)很大,不过可以发现\(f_{i,j,k,0/1}\)的值很小,同时在值相同的情况下\(k\)一定越小越优,所以改良一下:设\(f_{i,j,k,0/1}\)其中\(k\)表示收集了\(k\)张邮票,其余意义一致,其值表示满足该条件情况下的最小用时,转移同上。答案就是存在有意义的\(f_{i,j,k,0/1}\)的最大的\(k\)。
奥运公交
直接枚举换边暴力不太行,但是发现点数远小于边数。这启发我们从枚举点的思路出发考虑如何换边。枚举我们将要换的边的起点\(u\),考虑\(1\)到\(n\)和\(n\)到\(1\)的路径的可能情况。\(1\)到\(n\)和\(n\)到\(1\)实际上是一样的,下面仅对\(1\)到\(n\)进行说明。不妨假设换的边是\((u,v)\),那么有以下若干情况:
- 从\(1\)到\(n\)根本没经过\(u\);
- 从\(1\)不经过\((v,u)\)边走到\(u\),然后从\(u\)走到\(n\);
- 从\(1\)经过\((v,u)\)走到\(u\),然后从\(u\)走到\(n\)。
为了计算以上信息,我们需要求出:
- \(1\)到\(n\)不经过\(u\)的最短路;
- \(1\)到\(u\)的最短路;
- \(u\)到\(n\)不经过\((u,v)\)边的最短路。
看到第\(3\)个条件,好像要对于每一条边跑最短路,但实际上没有必要。我们可以在忽略所有\(u\)的出边的情况下跑出\(1\)到其它点、其它点到\(n\)的最短路。
那么对于\(1\)情况可以直接计算;对于\(23\)情况可以求出到达\(u\)的最短距离,而对于从\(u\)到\(n\)不经过\((u,v)\)的最短路,相当于把其他\(u\)的出边加上。可以通过最短路求出从\(u\)的每一条出边出发到达\(n\)的最短距离并记录最小值和次小值,如果翻转了最小值对应的边就用次小值贡献,否则用最小值贡献。
注意到图中点数小而边数大,可以使用\(O(n^2+m)\)的朴素Dijkstra,复杂度\(O(n^3+nm)\)。
火灾
首先将询问变为前缀相减并离线,那么询问变成了二元组\((P,T)\)表示对长度为\(P\)的前缀在\(T\)时刻进行询问。
设\(S_{i,j}\)表示在\(i\)时刻\(j\)位置的火势,考虑一个位置\(i\)的初始火势\(S_i\)在\(R\)从\(0\)不断变大时对序列的影响。它在\(1\)时刻会覆盖\(i+1\)、\(2\)时刻会覆盖\(i+2\)、……直到某一个时刻\(t\)覆盖了\(i+t\)之后发现\(S_{t,i+t+1}>S_i\)然后贡献停止。将时刻和覆盖的位置看做一个点的横纵坐标,它们恰好落在一条线段上。
如果转成坐标系上的问题,覆盖不那么好做,而求和是比较熟悉的数点问题似乎更好做。考虑差分,如果\(S_i\)在\(t\)时刻覆盖了\(j\),那么认为\(S_{t,j}\)在\(t\)时刻增加\(S_i - S_{t-1,j}\)。那么位置\(i\)的贡献就可以表示成若干个四元组\((i,L,R,\Delta)\)表示\(\forall j \in [L,R]\),\(S_{j-i,j} = S_{j-i-1,j} + \Delta\)。现在问题变为有若干条线段、每条线段对其经过的所有整点加上一个权值,多次询问横纵坐标小于等于某个值的所有点的权值的和。
考虑如何处理出四元组。从右往左维护\(S\)的递增单调栈,那么加入\(S_i\)时会pop掉若干位置,\(i\)就会覆盖这些位置所覆盖的区间,且每段区间增量一致,可以得到等量四元组。这样可以得知四元组数量是\(O(n)\)的。注意到\(S_i\)覆盖的区间连续,所以可以对\(i\)位置的所有四元组的\(\Delta\)差分,并将左端点全部设为\(i+1\),这和原来是等价的。这样我们可以记修改为三元组\((i,R,\Delta)\),对应上述四元组\((i,i+1,R,\Delta)\)。
当然直接数点还是不行,不妨考虑修改\((i,R,\Delta)\)对询问\((P,T)\)的贡献\((i < P)\),是\(\min\{R-i,P-i,T\} \times \Delta\)。接下来就是套路:将\([1,P]\)分为\([1,P-T-1]\)和\([P-T,P]\),前者满足\(T
对于\([1,P-T]\)区间贡献是\(\min\{R-i,T\}\)。我们可以以\(R-i\)作为下标,用树状数组维护当前加入的所有直线的\((R-i) \times \Delta\)和\(\Delta\)的前缀和。对于一次询问我们就查询\(\leq T\)的位置的\((R-i) \times \Delta\)的和和\(>T\)的位置的\(\Delta\)和就可以算出贡献;对于\([P-T+1,P]\)先转换成前缀相减形式,然后同理维护若干个树状数组。
复杂度\(O(n \log n)\)但是一个询问最多要拆成\(6\)个还要维护好几个树状数组所以跑起来还是比较慢的。