APIO2018~2022做题记录
APIO2018~2022做题记录
1.[APIO2021] 封闭道路
题意:一棵大小为\(n\)的树,有边权,设\(f(x)\)表示要满足所有点的\(deg\leqslant x\)所要删掉的边的边权和的最小值,求出\(f(0)\)到\(f(n)\)
思路:先考虑对于每个\(x\)计算答案。设\(dp[i][0/1]\)表示\(i\)向上连的边删或不删时的最小代价。转移时,对于\(i\)的每个儿子\(j\),有两种贡献,\(a_1=dp[j][0]+w\)表示删掉\((i,j)\)的贡献,\(a_2=dp[j][1]\)表示不删\((i,j)\)的贡献。我们考虑先取所有的\(a_2\),用堆来维护所有的\(a_1-a_2\),然后选最小的一些\(a_1-a_2\)把\(a_2\)替换掉,这一步可以用堆维护。单次复杂度\(O(n\log n)\)。
然后考虑正解。如果从小到大考虑每个\(x\),那么一个\(deg=x\)的点对\([x,n-1]\)是没有贡献的,对于每个点只需考虑\(x<deg\)的情况,这样的量级是\(\sum deg=n\)的。我们将\(deg\leqslant x\)的点视为无用点,其他为有用点,然后从每个有用点开始dfs,把无用点视为叶子,这时每个无用点的\(a_1=w,a_2=0\),它对它相邻的有用点的贡献即为\(a_1-a_2=w\),然后像暴力做法一样加入有用点的贡献,最后再加上一直弹堆顶到堆中元素为须删掉的边数时,堆中所有\(a_1-a_2\)的和就是答案。注意更新答案后要撤销加入的有用点的贡献,并撤销不断弹出堆顶直到堆中元素符合要求时删除的无用点的贡献,需要用可删除堆维护。总(均摊)复杂度\(O(n\log n)\)。
题意:在无向图上选3个不同的点\(a,b,c\),要求从\(a\)到\(b\)到\(c\)有简单路径,求方案数。
思路:考虑如果原图是一棵树,那可以简单地枚举\(b\)算答案,这启发我们建出原图的圆方树,再计算答案。对于一个点\(x\),如果是圆点,只要选择的点不是在这个点为根的意义下的同一棵子树里即可,即\(siz_y\times(siz_x-siz_y-1)\);如果是方点,计算的是这个点双里的一个点为\(b\),且\(a,c\)至少有一个在这个点双里的答案,即\((deg_x-2)\times deg_x\times(siz_x-siz_y)\)。最终用换根dp实现就是\(O(n+m)\)的。
题意:给定\(m\)组\([l,r]\),求不同的有序数对\(((i+\left\lfloor\frac{i}{B}\right\rfloor)\bmod A,(i\bmod B))其中(i\in[l,r])\)的数量。
思路:设\(i\)对应的数对与\(j\)对应的数对相等,那么首先有\(i\bmod B=j\bmod B\),设\(i+\left\lfloor\frac{i}{B}\right\rfloor=k_1A+r,j+\left\lfloor\frac{j}{B}\right\rfloor=k_2A+r\),那么有\((j-i)+\left\lfloor\frac{j}{B}\right\rfloor-\left\lfloor\frac{i}{B}\right\rfloor=(k_2-k_1)A\),把\(j\)表示为\(i+kB\),就有\(kB+k=(k_2-k_1)A\),自然,\(k\)最小可以取\(\gcd(A,B+1)\),因此循环节就是\(\dfrac{A\times B}{\gcd(A,B+1)}\)。
接着把每个区间对循环节长度取模,于是就变成了离线求区间交的问题,可以直接差分解决。
题意:给一张无向图,维护改边权以及查询包含一个点且每条边的边权都不小于给定权值的连通块大小。
思路:考虑如果没有修改,可以直接把所有边和询问按权值排序,然后从大到小加边,用并查集维护连通块大小。但是有了改边权的操作,可以考虑将操作序列分块。对每一块的操作,先将所有边按边权排序,将询问按权值排序。对于每一个询问,先将没有修改且边权大于询问的权值的边加入,然后将在询问之前被修改的边加入,求出答案后删除,这一步可以用可撤销并查集维护。设以\(B\)为块长,那么复杂度为\(O(\frac{Q}{B}m\log_2m+QB\log_2n)\),块长取\(\sqrt{m\log_2n}\)可以做到\(O(Q\sqrt Q\log_2m)\)。
题意:数轴上有\(n\)个点,每个点有位置\(x_i\),颜色\(t_i\) ,且在时间段\([a_i,b_i]\)中出现。
有\(Q\)个询问,每次询问在\(y_i\)时刻所有颜色中距离位置\(l_i\)最远的颜色。某个时刻下位置\(l_i\)和颜色\(c_j\)的距离定义为当时存在的所有颜色为\(c_j\)的点与\(l_i\) 距离的最小值。
思路:首先考虑对时间轴离线,那么就变成了动态加删点和查询最短的包含所有颜色的区间的长度。对于区间长度可以用二分来求,那么问题变成了如何判定一个区间是否存在所有颜色。这里直接采用一个经典的区间数颜色套路,对每个点上的每种颜色,维护它的前驱,那么如果区间\([l,r]\)包含了所有颜色,则区间\([r+1,n]\)的所有前驱都在\([l,n]\)中,即前驱的最小值不小于\(l\),这样就可以简单判定了。为了方便判定,可以在\(n+1\)处加入每种颜色的点,而且由于每个点可以有多种颜色,需要用\(muiltset\)来维护前驱。时间复杂度\(O(Q\log^2n)\)。
题意:有长度为\(n+1\)的链,初始联通,接下来的每一时刻,会有一条边的状态改变,或者查询点\(a\)和点\(b\)在初始到现在有多少个时刻是联通的。
思路:发现每修改一条边,会有一个矩形的值在该时刻被改变,于是可以考虑把操作放到矩形上,设点\(i\)往左能到的最远的点为\(st\),\(i+1\)往右能到的最远点为\(en\),那么\(i\)与\(i+1\)联通时,把矩形\([st,i],[i+1,en]\)加上\(T-t\),断开时减去\(T-t\),那么每一段联通的贡献刚好是\(\Delta t\),查询也直接是单点查。对于连续段,可以用\(set\)维护,而矩阵加可以用树状数组套线段树维护,复杂度\(Q\log^2n\)。
题意:构造一个排列,使得其上升子序列个数为\(k\),\(k\leqslant 10^{18},len\leqslant 90\)。
思路:首先考虑连续上升的一段序列,可以产生\(2^{len}\)个上升子序列,然后对\(k\)二进制拆分,每一位上的1对应一段,这样可以用\(\log^2k\)的长度解决。我们发现,这样做很浪费,明明可以不用每次都重新用一段,于是可以考虑在最长的一段后面接几个点,这些点可以和这一段的一个前缀共用子序列,就像这样
可是这样长度是\(2\log k\)的,还是超过了90,于是我们考虑怎么优化。我们发现,像这样,图中的绿、蓝两点,对应的是\(k\)的二进制下相邻的两位1,但是我们可以用一个紫色的点来直接代替这两个点
按照这个思路,我们就可以做到\(1.5\log k\)的长度来解决这个问题了。
题意:有一张带权无向图,每次询问有两个人从\(x,y\)出发,要求这两个人不能同时在一个点上,也不能同时经过一条边,允许多次走一个点或者一条边,要求最小化路径上的最大边权。
思路:一看到最小化路径上的最大边权,自然想到了最小生成树,于是考虑在\(\text{Kruskal}\)重构树上下文章。首先,如果要满足题面的条件,充要条件是路径不是一条链,于是问题转化成了怎么判定。我们对于每个点,维护这个点所在所在的连通块是不是链,如果是链还要维护链的链头、链尾。现在,如果加入了一条边\((x,y)\),根据\(x,y\)所在连通块是不是链分类讨论:只要有一个点所在的连通块不是链,那么这个新连通块就不是链;反之,如果两个点都是链的端点,那么这两条链会合并,否则就会形成一个新的不是链的连通块。对于询问,答案就是两点在\(Kruskal\)重构树上的\(lca\)的点权。复杂度\(O(m\log m+q\log n)\)。
题意:一张\(n\)个点的有向图,初始对于\(0\leqslant i\leqslant k-2\),有边\(i\rightarrow i+1\),且前\(k\)个点称为关键点。现在会加入一些边,每加入一条边查询是否存在一个包含关键点的环。
思路:假如存在环,那么环与主链\(0\rightarrow 1\cdots k-1\)的交是一段子链\(L\rightarrow L+1\cdots R\),且\(R\)能到\(L\)。考虑如果子链包含边\((i,i+1)\),那么说明\([i+1,k)\)能到\([0,i]\),于是考虑维护两个点集。设\([1,i]\)能到的点集\(S_1\)和\([i+1,k)\)能到的点集\(S_2\),这样如果最终环不包含边\((i,i+1)\),那么换上的所有点一定同时属于\(S_1\)或同时属于\(S_2\)。设一个点的状态为\(0/1/2\)表示不属于\(S_1,S_2\)和属于\(S_1\)和属于\(S_2\),那么,只有一条边的端点都属于\(S_1\)或\(S_2\)时,才需要进入子区间,于是考虑分治。
对于一个区间\([l,r]\),设\(E_{l,r}\)表示对这个区间有影响的边集,那么对于一条边\((x,y)\),有3种情况:
1.x的状态为2,y的状态为1,那么显然这时已经存在环了,可以直接退出。
2.x的状态为2,那么从y开始dfs,把经过的状态为0的点状态改成2,删除所有经过的边,将其和\((u,v)\)下放到\(E_{l,mid}\)。
3.y的状态为1,那么从x开始在反图上dfs,把经过的状态为0的点状态改成1,删除所有经过的边,将其和\((u,v)\)下放到\(E_{mid+1,r}\)。
这样就只剩一个问题,环和主链可能没有交边,那么把\(u\)加1,把大于等于\(k\)的\(v\)加1,并钦定主链为\(0\rightarrow1\cdots k\),这样所有以关键点开头,以关键点结尾的路径编号都加1,这样就和主链有交了。
题意:平面上有\(n\)个圆圈,每次会选择半径最大的圆并删掉它已经所有和它有交的圆,求出每个圆是被那个圆删掉的。
思路:我们发现,解决这个问题的关键肯定是选择了半径最大的圆后减少枚举其他圆的量。但是这是在二位平面上的,怎样可以减少枚举量呢?我们考虑按照最大圆直径把原图划分成一个个正方形的块,这样与最大圆有交的圆一定出现在这个方格以及相连的8个方格里,就可以大大减少枚举的量。但是问题又来了,如果最大圆半径过大,那么接下来的枚举量还是很大,于是考虑重构,如果当前圆的半径小于方格大小就重构,这样每一次重构边长至少会减半,只会进行\(\log n\)次重构,而且可以证明每个圆被枚举的次数是常数,于是在\(O(n\log n\log R)\)的时间内解决了这个问题。
题意:题意太长了,不想写了。。。
思路:发现如果我们确定了哪些点可以作为开头,我们就可以直接贪心求出答案,现在问题就变成了如何判断一个点是否能作为开头。我们考虑一个朴素的dp,设\(f_{i,j}\)表示现在用\(j\)号承包商涂第\(i\)号墙,往后最多能涂多少。这样如果一个点的dp值不小于\(m\)就说明可以作为开头。可是这样dp是\(O(nm)\)的,于是我们考虑优化。题目中有\(\sum f(k)^2\leqslant 400000\),这就代表着每一段墙壁最多可以被\(\sqrt n\)个承包商粉刷,于是可以考虑只记录可以粉刷这一段墙壁的承包商的dp值。在实现上,就是用滚动数组,且每次只更新可以粉刷这一段墙壁的承包商的dp值即可。
题意:有一棵树,每个点度数不大于3,你需要给出一个排列,使得\(dis(a_0,a_1)\geqslant dis(a_1,a_2)\cdots dis(a_{n-2},a_{n-1})\),你每次可以询问两点距离和一个点为根时另一个点的子树大小,要求询问次数不大于\(4n\)。
思路:容易发现,以重心为根,可以每次选择当前子树最深的点,跳到另一个子树最深的点,根据重心的性质一定有解。于是第一步是找重心。这一步可以先钦定0为根,求出每个点子树大小\(siz_x\),那\(siz_x>\frac{n}{2}\)且\(siz_x\)最小的点就是根,如何可以求出每个点深度以及在哪棵子树里,这些总共不到\(4n\)。然后根据有几棵子树分类讨论。两棵的情况可以直接处理,三棵的情况要根据最大的子树大小是否等于剩下两棵之和分类,如果相等,把剩下两棵合并后按两棵子树的方法处理即可,否则删掉深度最深的点。
题意:长为\(n\)的序列,每个点有高度且互不相同,每个点可以跳到左、右第一个比它高的点,求从\([A,B]\)中任选一个起点,跳到\([C,D]\)中任意一个点的最小步数。
思路:首先,每个点能一步跳到的点可以直接用线段树二分求出。我们先考虑\(A=B,C=D\)的情况,肯定是每次跳到比\(h[C]\)小且最大的点上,这样一定最优。再考虑更一般的情况,首先可以发现,如果\([B,C-1]\)的最大值大于\([C,D]\)的最大值,一定跳不过去,因此这样无解。对于有解的情况,可以先假设从\([A,B]\)中的最大值所在位置\(x\)开始跳,这样一定最优,不过如果\([x,B]\)的最大值大于\([C,D]\)的最大值就跳不过去,于是只能从满足\([x,B]\)的最大值小于\([C,D]\)最大值的最左的点开始跳,只要跳到的点不到\([C,D]\)的最大值就可以跳,否则就只能一路向右跳,只要跳到\([C,D]\)中即可。在实现上,求满足\([x,B]\)的最大值小于\([C,D]\)最大值的最左的点可以也用线段树二分,跳到过程需要两个倍增数组。复杂度\(O(n\log n+q\log n)\)。