签到题,按照题意模拟就行了,由于lincong将题目中的“中心对称”看成了“轴对称”,导致WA了三发还不知道为啥,之后重读题目才发现了这个锅,修好之后就A了。
I题的题意很好理解,求给定图的最小直径生成树。
问题的关键在于求出图的最小直径,换句话说,就是要求出一个点,使得它到图中各点距离的最大值最小,且这个点可以在一条边的中间。类比于树的中心,我们可以称这个点为图的中心,简称“图心”。
一个十分显然的思路是枚举每一条边,看“图心”是否在这条边上。
具体来说,对于一条边(u,v),我们假设“图心”就在这条边上,下一步是选择“图心”的位置。由于“图心”的可选位置有无数多个(可以从u一直过渡到v),所以我们可以把图中某一个点到“图心”的距离,想象成一个函数f(x),自变量x是“图心”到u(或v)的距离。而f(x)的图像就是一条折线段,它的最大值在[0,w](w指边权)中的某处取到。
这样,图中各点到“图心”的距离的函数图像,形成了一个个折线段。如果我们把这些折线段全部叠加到一起,并取此时图像中最上方形成的一条折线段(见下图),那么这条折线段,就是“图心”到图中各点距离的最大值。
这时,我们只需要找到这条折线段中最低的交点,那么这个交点的横坐标,就是“图心”所在的位置。因为选取这个点作为“图心”后,它到图中各点距离的最大值最小,实现了我们一开始提出的目标。
那么,应该如何寻找这个最低的交点呢?
首先,我们需要过滤一些没有用的点。什么叫做没有用的点呢?观察下图:
最上方的红色折线段就是我们需要的折线段,显然,它是由3、4、5这3条折线段相交构成的,所以1、2这两个点属于没有用的点。
如果记两点之间距离为dis(i,j),那么对于1号点来说,因为dis(1,u)<dis(3,u),dis(1,v)<dis(3,v),所以1号点形成的折线段,完全处于3号点形成的折线段的下方(易知折线段斜率相同),因此1号点是一个没有用的点。对于2号点同理。
当我们去除掉没有用的点之后,图像变成了这个样子:
根据观察,我们发现了一个很有用的性质:如果dis(i,u)是按照升序排列的,那么dis(i,v)一定是按照降序排列的(由于折线段斜率相同)。因此,这些剩下的折线段之间的交点,一定是按照dis(i,u)(或dis(i,v))排好序后,相邻的两条折线段相交出来的!这下最低的交点对应的距离ans就迎刃而解了。
按照这个方法,我们可以求出每一条边对应的ans,而最小的ans所对应的那条边,就是这幅图真正的“图心”所在的边。以这条边作为起点生成的树,就是我们要求的最小直径生成树,最小直径就等于ans * 2。
有了思路之后,代码实现并不是很难,经过几次WA之后,lincong发现并修正了一些小错误(如数组越界、精度问题、int溢出等),最后终于在2.5h左右的时候A了这道题。
H题的题意也不难理解,给定两个长度为n、元素为1-n的序列,每次只能交换其中一个序列的相邻两项,求最小交换次数,使得这两个序列对应项的差值的总和最大。
设不动的序列为a序列,交换的序列为b序列。1mszs在短暂的思考之后,认为只需把b序列中小于mid(中值)的数看作1,大于mid的数看作2,让b序列中的1都移动到a序列中2的位置、b序列中的2都移动到a序列中1的位置即可。
具体来说,只需要将b序列中的1进行移动,只要所有的1都移动到了指定位置,那么所有的2自然也移动到了指定位置。
而对于1的移动,只需要从前到后依次在a序列中找2,在b序列中找1,找到一组后,它们俩的位置之差就是需要移动的次数。将它们俩的位置之差加入sum,然后接着向后移动,重复以上操作,直到全部查找完毕,此时的sum就是题目要求的最小移动次数。
代码实现起来非常简单,思路也感觉没什么问题,但是交上去之后却发现WA on test 7。查了半天也没查出来错误的1mszs把代码扔给了lincong,然后自己去看G题。
lincong接手H题之后,想了一会儿,感觉这个思路没有毛病,应该是某个奇怪的地方写挂了。果不其然,在处理n为奇数的情况时,1mszs忘记给查找数组用的指针清零了,修正了之后AC。
G题的题意是这样:给定一幅有向图,每一条边有一个边权,且边权之间互不相同。求从s到t的边权序列中,字典序最小的那个序列。若不存在s到t的通路,输出“IMPOSSIBLE”;若字典序最小的序列长度超过1e100,输出“TOO LONG”;否则输出那个序列。
A掉A题的lincong看了一会G题,感觉没有思路,于是滚去看I题。
把H题代码扔给lincong的1mszs接手了G题,首先发现这题是一个贪心,每次能走就选择边权最小的边去走(前提是下一个点是可以到达终点的点)。至于判断一个点是否是可以到达终点的点,只需反向建图,从终点dfs一遍即可。
其次1mszs注意到,输出“TOO LONG”的情况,一定是按照贪心的策略走的时候,碰到了环,那么就会这么无限循环下去。
有了思路的1mszs开始写代码,不一会儿就写完并调试好了,但是交上去之后却发现WA on test 6。查了半天也没查出来错误的1mszs把代码扔给了lincong,然后自己去看F题(似曾相识的经历)。
lincong接手G题之后,感觉应该又是某个奇怪的地方写挂了。在手动模拟了一组数据之后,发现1mszs忘记处理终点包含在环内的情况了,在这种情况下仍然输出了“TOO LONG”,修正了之后AC。
时间已经过去了3.5h,1mszs和lincong都略感疲惫,但是题还是得继续往下看。
在观察了每道题的通过人数之后,1mszs和lincong认为剩下的题中,只有F题和J题可以尝试一做。于是看完了这两道题的题意之后,1mszs打算去研究F题,而lincong决定去研究J题。
J题的题面比较冗长,大意如下:
给定一条凸弧,弧上有许多等距离的点。在弧的下方,有n条连接着弧上两点的线段,且线段之间两两互不交叉。每一条线段有一个权值。题目的要求是选定其中的一些线段,使得从弧的每个点向下看,最多都只有k条线段被选中,且被选中的线段的权值之和最大。对于每一个k(1-n),输出最大的权值之和。
由于线段之间两两互不交叉,所以我们可以类比线段树,把这些线段全部建到一棵树上(令子节点的区间包含在父节点的区间中)。这样在dfs的时候,我们就可以把子树的权值,不断地合并到父亲节点上。
具体来说,我们需要对每一个节点开一个优先队列,用来存放当前节点包含的区间[l,r],所能合并出来的所有权值(叶节点的优先队列中就只有本身的权值)。那么只需在dfs回溯的时候,把子节点的优先队列上的信息,全部并入父节点的优先队列即可(按秩合并)。
这样的话,在根节点的优先队列中,就存放着合并一层所能得到的所有权值(且每个权值都是当前最优的选择)。查询的时候,只需要从根节点的优先队列中不断取出队首,就能得到合并k层所能得到的最大权值了。
举个例子:
6
1 2 10
2 3 10
1 3 21
3 4 10
4 5 10
3 5 19
将题目中给出的这些线段按照读入顺序编号(根节点编号为n+1),然后把它们建到树上后,是这个样子:
在dfs的时候,我们首先搜到了1号节点,是一个叶子节点,所以直接将10并入3号节点的优先队列。接着搜到了2号节点,同样把10并入3号节点的优先队列,3号节点的优先队列变成了[20]。最后回溯到3号节点,将自己的权值加入优先队列,此时3号节点的优先队列变成了[21,20]。对于6号节点同理。
当回溯到7号节点时,我们取出3号优先队列和6号优先队列的队首,合并,然后加入7号优先队列,这时就得到了合并一层所能得到的最大权值41。同理,得到了39。
代码实现起来难度也不算太大,最后在4h左右的时候通过了这题。
然而,天有不测风云,正当1mszs和lincong讨论F题的时候,一帮不速之客闯入了研讨室。原来,是1mszs预约的研讨室到期了。因此,1mszs和lincong迅速收拾东西,草草结束了本次VP。
应该是个线段树,不过目前一直卡在WA on test 20,没有进展。