UOJ Logo vfleaking的博客

博客

UOJ Round #4 题解

2015-01-02 22:24:29 By vfleaking

元旦三侠的游戏

from keavil

算法一

对于第一个测试点,我们有 n=2 。那么必定有 a=2b=1

显然,无论我们增加 a 或者是 b,都会违背 abn 的要求。那么这种情况必定是先手必败的。因此直接输出No就能获得10分。

算法二

这是一个博弈问题。我们考虑用 fa,b来表示当前的游戏状态。fa,b1 表示当前的 (a,b) 是先手必胜状态,否则表示当前的 (a,b) 为先手必败状态。

然后我们就可以用记忆化搜索来转移了。注意到 a 显然不能超过 n,而 b 不能超过 logn 。那么就可以在 O(nlogn) 的时间内处理出全部的状态了。

算法三

注意到,对于一个 ba 必定不能超过 nb 。那么我们实际上状态数只有: b=1lognnb

而如果一个状态 (a,b) 满足 a>nb+1 ,那么显然不能再增加b了,而我们容易算出 nb 这个值,那么我们根据它与 a 的差的奇偶性就可以直接得出当前状态。

那么,我们的状态数就只有: b=2lognnb。这个值显然只有 O(nlogn) (实际上远小于这个值)

复杂度: O(nlogn)

元旦激光炮

from vfleaking

元旦快乐!送给大家一道交互式的水题当元旦礼物!

算法一

我们可以用 na+nb+nc 次询问把整个数组给询问出来,然后排个序求出第 k 小。

可以通过 1 号测试点获得 10 分。

算法二

显然可以二分答案嘛!二分第 k 小的值,然后在三个数组里二分统计小于等于这个值的有多少个。

在长度为 n 的数组里二分需要调用 log2n 次,而答案的范围是 109 需要二分 30 次,所以总次数最坏是 30×17×3=1530 次。显然这离 2000 次调用的限制差得远。

可以拿到每个测试点的 6 分获得 60 分。

如果你特判掉了 1 号测试点使用暴力,那么可以拿到 1 号测试点的 10 分获得 64 分。

算法三

注意到有三个超良心的测试点,其中一个数组是空的,只有两个数组。我们可以假想这两个数组的长度为 k,然后这里有一个非常有趣的给两个数组取中位数的算法可以解决此问题。

现在手上有两个数组 a,b 长度均为 nn 为偶数。我们记 i=n/2j=n/2。(这里假设数组下标从 1 开始)

我们比较一下 aibj 的大小。如果 ai<bj,那么:

  • a1ai 均比 bjbn 要小,这说明 a 中有 ni 个元素,b 中有 nj+1 个元素比他们大,总共 n+1 个元素比他们大。
  • 另一方面,这说明 a 中有 i 个元素,b 中有 j 个元素比 bj+1bn 要小,总共 n 个元素比他们小。

所以,我们可以去掉 a 的前 i 个元素和 b 的后 i 个元素而中位数不变,然后递归下去就行了。对于aibj 的情况可以类比。最后递归到 n=1 时,暴力下就行了。

这样只用 2×(log2n+1) 次调用即可解决问题。对于 k2×105,调用次数只有 2×18=36 次。

结合算法二可以获得 72 分。如果再结合算法一可以获得 76 分。

算法四

当我知道有个算法三的时候我惊呆了……其实我想表演的是另一个神奇的算法。

首先我们把数组的长度扔进垃圾桶!我们想象数组是无穷长的,即在原数组后面补无穷个无穷大。

然后我们取 l=k/3。我们来比较 al,bl,cl。我们假设 al 是这三个数中最小的。

那么我们看看最多有多少个数比 al 小,为了说话方便我们无视两个数相等的情况。a 数组中肯定是 l1 个,b 数组中由于 blal 大,所以最坏情况是 b1bl1 都比 al 小,c 数组也是一样。于是最多 3l3 个比 al 小。所以 al 的排名至多是 3l2<k

于是我们可以甩掉 a 数组的前 l 个元素然后 k=kl,递归下去。注意到每次 k 的大小都减少了三分之一,到最后 k2 时我们可以用暴力 6 次调用解决问题。总调用次数满足递推式 T(n)=T(nn/3)+3,所以会调用次数不超过 log3/2k+4

对于 k3×105,调用次数只有 96,足以解决此题。

可以通过所有测试点获得 100 分。

考试时有一些人想到了这个算法,但是你们基本萌萌哒把边界写残了?只有感动中国人物 liuzurang AC啦~我觉得我的样例给得不弱啊……反正我是用同一个数据生成器生成的……

追击圣诞老人

from wangyisong1996

题目大意

给一棵树,每个点有权值wi (wi>0)。

对于第i个点给定三个参数ai,bi,ci, 第i个点只能走向ai,bi,ci这三个点在原树中两两之间的路径上的点。

一个长度为l的序列s1,s2,...,sl的权值定义为i=1lwsi, 这个序列中对于任意1i<l都要满足si能走向si+1

求权值前k小的序列的权值。

算法一

暴力枚举所有长度不超过k的序列,复杂度O(nk),能通过1, 2号测试点。

算法二

定义W(A)表示序列A的权值。

我们把所有序列建成一张图,每一个序列向它最后加一个点形成的序列连边。 那么这张图满足堆性质:如果u连向v,则W(u)<W(v)

用一个优先队列维护已经扩展到的点(序列)。

一开始将所有入度为0的点(长度为1的序列)放入优先队列里, 然后进行k次操作,每次操作是弹出优先队列中权值最小的序列(记为A), 然后将A连向的所有序列放入优先队列里。 第i次操作弹出的序列就是第i小的答案。

注意这张图不需要完全建出,只要在访问到x这个点时将x的出边建出即可。

由于一个点会连向O(n)个点, 该算法的复杂度为O(nklognk), 能通过1 4号测试点。

http://uoj.ac/submission/4503

算法三

对于上一个算法中图上的每个点,将它的所有出边按权值从小到大排序, 那么就可以用左儿子右兄弟的方法将度数减少为2,这样优先队列中只会被放入O(n+k)个点。

由于一个序列最后能加的点只跟这个序列的最后一个点有关, 所以只要将每个点能走到的所有点按权值排序即可。

时间复杂度O(n2+klog(n+k)),空间复杂度O(n2+k)。 能通过1 8号测试点。

http://uoj.ac/submission/4504

算法四

上一个算法的瓶颈是将每个点能走到的点按权值排序。

我们可以将每个点能走到的点按权值建一个小根堆, 优先队列中记录这些信息:(权值, 最后一个点, 指向一个堆的指针)。

弹出一个序列(W,x,H)时, 只要将(W+wH.l.idwx,H.l.id,H.l)(W+wH.r.idwx,H.r.id,H.r)(W+wHeapx.id,Heapx.id,Heapx)放入优先队列即可。

其中H.id表示堆H中权值最小的点的编号,Heapi表示一个包含点i能走到的所有点的堆。

现在问题转化成,对每个i求出Heapi

我们用可并堆(如左偏树)预处理第i个点到根的路径上前2j个点的堆, 然后每个点能走到的点可以拆成三条链,于是可以用倍增得到。

时间复杂度O(nlog2n+klog(n+k)),空间复杂度O(nlog2n+k)。 能通过第1 12号测试点。

http://uoj.ac/submission/4505

算法五

考虑算法三, 如果我们能在O(f(n))的时间内求得第i个点能走到的点中,权值第j大的点, 就能在O(g(n)+klog(n+k)+kf(n))的时间内解决原问题,其中O(g(n))的时间用来预处理。

算法三中f(n)=O(1), g(n)=O(n2)

我们用主席树(可持久化线段树前缀和)来维护1i路径上点的权值,就可以使f(n)=O(logn), g(n)=O(nlogn)

于是总的时间复杂度为O(nlogn+klog(n+k)),空间复杂度为O(nlogn+k)

能通过1 16号测试点。 对于n=5×105的数据会MLE。

http://uoj.ac/submission/4506

算法六

考虑算法四, 对于一条链(a,b),将这条链上的点建成一个堆H, 那么H.id就是这条链上权值最小的点。 我们将这条链从这个点处断开, 那么剩下的两段分别对应于H.lH.r

我们用一个二元组(a,b)表示(a,b)这条链的堆, 就不用预处理出算法四中的Heapi了。

复杂度为O(nlogn+klog(n+k)+kf(n)), 其中f(n)为求出一条链上权值最小的点的时间复杂度。

用 倍增 or LCT or 树链剖分(要预处理每条重链的前缀min) 都可以做到f(n)=O(logn)

那么时间复杂度就是O(nlogn+klog(n+k)),跟算法五一样。

如果你用倍增,空间复杂度是O(nlogn+k),很有可能会MLE。

如果你用LCT,空间复杂度是O(n+k)的,但是常数太大,很有可能会TLE。

用树链剖分就可以通过所有测试点了。

http://uoj.ac/submission/4634

评论

前排
前排好评!
前排
中排
中排
后排
后排
后排
大后排
后排orz
大后排orz
@aslmaswd 太神了!
大后排
T2 的二分复杂度更加优秀。。。 http://uoj.ac/submission/243331
是啊 其实T2二分数组下标更加优美
第三题可以二分答案然后优先队列算路径数量每次暴力跳a,b,c的祖先把已经超过二分mid的点用并查集跟父亲并起来可以做到空间线性,时间O((n+k)logn log|ans|)。不知道是不是因为常数太大了只得了80分。 http://uoj.ac/submission/299830

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。