首页 > 代码库 > HNOI 2014
HNOI 2014
D1T1:画框 frame
题意:给你两个n阶正整数方阵,请你求最大的\( \sum_{i = 1}^{n} A_{i, p_i}\times \sum_{i = 1}^{n} B_{i, p_i} \)其中\({p_i}\)是一个n的排列。\(n \le 70\)。
如果A=B,这就是一个二分图最大完美匹配问题,那么我们可以用费用流或者KM算法解决,然而A却可以不等于B,我们需要另辟蹊径。
当年我看到这道题就只会随机化乱搞,现在看来,如果仔细的思考还是可以搞出来的。
想到,我们可以求出对于A来说的最小值,附带一个对于B的值(即\(\sum_{i = 1}^{n}A_{i, p_i}\)和\(\sum_{i = 1}^{n}B_{i, p_i}\)),和对于B来说的最小值,附带对于A的值。不妨将以上的值抽象成二维平面上的点p(x,y)和q(x,y)。那么答案一定是基于对上述做法调整,因为我要适当增大p(x,y)中的某一个值,而减小另外一个值,使得x*y更小,这另我们想到了双曲线,我们要求的点一定位于过p(x,y)的双曲线之下,那么由于双曲线是下凸的,就是求一段下凸壳上点。求下凸壳。由于没法使用卷凸包法或者水平序法(这样会导致能通过遍历所有点(\(n!\)个点。。)求出下凸壳上所有点从而进行了多余计算,而我么只要求一个xy坐标相乘值最小的点),考虑分治。
分治法求下凸壳上的xy乘积最小的点。连结上述求出的两个点p,q,要求的点一定是p或者q或者在d连线的左下方,而从连线向左下推进最远的点t也一定位于凸壳上,那么答案就是t或者分别以p,t向下做的凸壳上的点或者t,q向下做的凸壳上,于是便可进行递归分治下去,复杂度为求O(最小完美匹配)*O(log在下凸壳上的点数),下凸壳上的点数是log(n!)级别的(不要问我怎么知道的)。至于如何求t点,将过p,q的直线方程写出来,就是:\((y_q-y_p)x+(x_p-x_q)y+c=0\)c是一个常数,那么我们要最小化ax+by,只要把g[i][j]设成A[i][j]*a+B[i][j]*b就行了。
code
D1T2:世界树 worldtree
题意:给你一棵树和q个操作,每次要你求m[i]个节点h[1],h[2],…,h[m[i]]所管辖的点数。定义某个点被h[p]管辖当距离这个点最近的唯一点是h[p]或者h[p]的标号最小。n≤300000, q≤300000, m[1]+m[2]+…+m[q] ≤300000。
当年考试前考过一道相似的题,没去改,考试时还是不会;现在还是不会。望后人警惕,考过的题一定要改,特别是出题人在身边的,不懂的一定要天台见(讨论题目啊我当然是这个意思)。
没事想想暴力:每次枚举所有点,找到离自己最近的点。复杂度n*n*q。
我们是否做了重复计算?对于一些点,我们并不要再一次枚举的,比如有那么一条链,或者有那么一颗子树,没有被选中的点,那么每次只要给最近的那个点加size就行了。
引入虚树的概念:一颗树的虚树指包含了所有给定点,并收缩度数为二的点的最少边数的联通子图。
对于每个询问,建出这些点的一棵虚树。所谓虚树就是用最少的边使得这些点连通,并将度数为2的点都给缩起来。
一个性质是,由个点建出的虚树中的节点数为O(k)。
虚树的建法也很简单,把点按照DFS序排序,那么相邻两点(首尾视作相邻)的LCA以及原本的点就构成了虚树。
我们求出虚树上到每个点的最近点。由于有上面那个性质,我们可以暴力BFS求。
现在的问题就是被我们缩掉的那些点,以及这些点之外的若干子树。
如果被缩起来的一条边两端的最近点相同,那么整条边上被缩起来所有点的最近点就是两端的最近点。
否则一定存在一个位置,以其为界,两边的最近点分别是两个端点的最近点。
这个只需要知道两个端点的距离以及两端到最近点的距离,用树上倍增就能求出来。
而一棵虚树外的子树的最近点就是其在虚树上的根的最近点。
那么这个问题就解决了。复杂度大概是(n+k)log(n)。
code
D1T3:米特运输 meat
题意:求修改尽量少的节点的值,使得所有每个节点的孩子节点的值相同且和为该节点的值。
当年被卡题意了,看到描述长度读都没读题。。sign。。sign。。后人警惕啊!描述越复杂,题目越简单,因为出题人只能用语文来提高难度了。
注意到如果确定某个节点的值,那么整颗树都确定下来的,那么我们可以枚举确定那些点,\(O(n^2)\)。50分到手。暴力code。
AC做法也很好想的。用根节点的取值代替整颗树的状态,那么对于每个节点不变求出根节点的取值,取众数便是答案。是\(O(n^2)\)的。这等价于,从上往下推,让根节点随意取个值,然后推出每个节点的值与根节点的倍数关系,是\(O(n)\)的。注意单纯这样搞可能要用到高精度,取对数即可。
code
D2T1:抄卡组 hs
题意: 给定N个字符串,包含小写英文字母、数字、和可以匹配任意长度个的字符(包含0个)通配符"*",问所有字符串是否两两匹配。
做法:两个Judge:Jud_with_tp() 当所有串都有通配符时只需要所有串的不带通配符的前缀和后缀相同即说明所有串都相同。 Jud_with_kmp() 当有串不含通配符时,取最短的那一个S,其他的串就相当于被通配符划分成了多个不含通配符的子串,此时就只要按顺序往后,所有子串都能在S中一一匹配到,则说明这两个串是相同的。可使用kmp,不过觉得暴力匹配也能过,没试了。
复杂度:Jud_with_tp() O(nLen) Jud_with_kmp() O(nLen) 都是线性的。
写的很丑,谨慎观看。
code
D2T2:道路堵塞 road
题意:给个有向图,问依次去掉最短路上的边后的最短路的长度。
考场上写了暴力,然后发现边比较少,于是加了个求桥边的判-1,目测写错了。
如果是无向图,从1到n,从n到1,两次dijkstra。从最短路上某条边E1开始,到达某条边起点;反方向从E2进行到达这条边的终点。那么若最短路上从E1到E2之间的边被截断,就可以从E1到这条边再到E2,跳过断路的这一部分,相当于求并联在去掉边两端的路径。用线段树维护最小值即可。
以上做法能过。。。但题目要求的是有向图。。对于二元环来说显然不可操啊。。。于是vfk看不下去了,出了组数据把我(和很多人)卡了。。目前只能暴力spfa+堆优化。。
D2T3:江南乐 game
题意:给你N堆棋子,轮流把不少于f个子的堆分成至少两堆,请问先手是否必胜。
我是傻到什么地步了。。竟然把0个子也算一堆,导致从考场到前一天一直以为题目有问题。我也不会SG函数嘎,下面是转的http://blog.csdn.net/gromah/article/details/27326991
对于10分的数据:明显当石子数大于1时,先手胜。因为只有一堆,先手只要把石子全拆成只有1个的若干堆就赢了,但如果本来就只有1个石子,就是后手赢,复杂度O(1)。
对于70分的数据:直接暴力求在当前的 f 值下每个石堆对的的sg值即可。每堆石子有O(n)中分裂方式,每个分裂方式的sg求值是O(1)的,有O(n)个石子,复杂度O(n ^ 2)。
对于100分的数据:我们考虑两个性质
1、(n / i)(商向下取整),至多有 2 * sqrt(n) 个不同的结果。
2、如果 n / i == n / (i + 2) ,那么通过将石子分裂成 i 份得到的 i 堆石子的sg值异或和与将石子分裂成 i + 2 份石子所得到的结果相同。
如果上述两个性质是对的,我们就可以只枚举 n / i 来求解,因为有很多分裂方式在本质上是相同的,我们完全可以不考虑。
现在我们来证明这些性质:
证明性质1:在1 - sqrt(n) 之间,因为只有 sqrt(n) 个数,所以 (n / i) 在1 - sqrt(n) 之间当然最多只有sqrt(n) 个结果。
在 sqrt(n) - n 之间,因为 n / n = 1 , n / sqrt(n) = sqrt(n),n 除以该范围内的数的结果必然在1 - sqrt(n) 之间 。所以 (n / i) 在 sqrt(n) - n 之间也只会出现sqrt(n) 个不同的结果。
综上,在1 - n之间,(n / i) 至多有 2 * sqrt(n) 个不同的结果,性质 1 是正确的。
证明性质2:因为把 n 个石子按照题目要求分成 i 份,会产生 n % i 个石子数为 n / i + 1 的石头堆,i - n % i 个石子数为 n / i 的石头堆。(这个挺显然的)
而把 n 个石子按照题目要求分成 i + 1 份,就相当于在分成了 i 份的基础上,从 n / i 个石子数为 n / i + 1 的石头堆中,各拿出1个石子,组成一个新的石子数为n / i 的石头堆。
而把 n 个石子按题目要求分成 i + 2 份的话,就相当于做了两次组新石子堆的工作,其石子堆的奇偶性不变,因为你操作了两次,所以各种石子堆的改变量就必然是2的倍数,既然这样,石子堆的sg值异或和就不会变。
所以性质 2 是正确的。
好的,现在我们保证了这个算法的正确性,来说说具体做法吧。
对于每个石子数n,我们枚举它的 n / i 值,然后取分裂成 n / i 份和 n / (i +1) 份的两种情况就可以了。
实现过程中,我们还可以进行记忆化搜索来提高程序的效率。
每个石子数的sg求值是 O(sqrt(n)) 的,有 O(n) 个石子,所以时间复杂度是 O(n * sqrt(n)) 的。
HNOI 2014