图片

NOIP提高组模拟赛第4场题解

首先非常感谢大家参加这场NOIP模拟赛。

这次题应该比前几天都简单吧,题目难度和部分分都是参照往年NOIP的中档题确定的,从得分情况来看,得到满分和高分的人比之前几场比赛都要多不少。

300分:3人

250分以上:5人

200分以上:11人

150分以上:28人

100分以上:51人

以下是这场模拟赛的题解。


文件改名

本题难度与NOIP2015 D1T2相近。

100分:25人

85分以上:39人

60分以上:61人

算法一

首先我们发现 si=ti 的文件是不影响答案的,我们把它们扔掉。

使用搜索算法,每次枚举一个文件 i 改成什么名称。虽然改名可以改成任意字符串,但我们只关心是改成 ti 还是非 ti ,这样搜索空间就有限了。

这样可以通过 n 比较小的数据。由于这些数据的文件名都是一个数字,可以用大小为 10bool 数组判断文件名是否冲突。

该算法视实现方式优劣,期望得分30至50分。

算法二

对于满足特殊性质2的数据,我们发现,把每个文件名直接改成 ti 一定不会冲突。

因此去掉 si=ti 的文件之后,剩下的文件都只需要改一次名,这样次数显然是最少的。输出剩下的文件名个数即可。

期望得分35分,结合算法一期望得分50至60分。

算法三

算法二什么时候能产生反例呢?我们发现,如果文件 i 存在一个文件 ji 使得 sj=ti ,那么要先对 j 改名才能再对 i 改名,而当每个文件 i 都存在这样的 j 时,任何一个文件 i 都不能直接改名为 ti 了。

同样先删去满足 si=ti 的文件,然后对每个文件 i ,若存在文件 ji 使得 sj=ti ,则连一条 i j 的边。这样可以得到一个各点入度和出度不超过 1 的图,例如

8
a b
b a
c d
d e
f g
g h
h f
i i

这些文件构成的图如下:

1 ---    3   5 --> 6 --> 7
|    |   |   ^           |
|    V   V   |           |
 --> 2   4    -----------

对于不在环上(即在链上,如上面的 3,4)的文件,我们可以按照链的逆序完成每个文件的改名,每个文件只要改一次。

对于在环上的文件(如上面的 1,2 以及 5,6,7 ),则要先将某个文件 i 的文件名改成一个随机串(这样指向 i 的边就会消失),然后再按链逆序完成文件的改名,于是大小为 x 的环需要至少 x+1 次改名。

因此找环统计即可求得答案。找环只需要从每个未被标记的点 i 开始,不断访问 i 连向的点并把经过的点打上标记,如果最后能回到 i 就找到了一个环,否则说明 i 及其到达的点不在环上。

如果暴力建图,即对每个 i 枚举满足 sj=ti j ,时间复杂度 O(n2),期望得分70分。

结合一些特殊数据可以得到更多分数。例如:

对于测试点15,16, si,ti 都是值不小于 106 的正整数,所以可以开一个大小为 106 的数组,对每个数 x 存满足 si=x 的文件 i 编号,可以通过这两个测试点,期望得分80分。

对于剩余数据,按照算法二输出剩余文件个数,可以通过测试点17,期望得分85分。

算法四

对于一般数据,同样容易处理。

由于文件名不长,每个文件名可以转换成一个不超过10位的64进制数,大小不超过 260,可以用64位整数类型存储。

将文件按 si 排序,之后对每个点 i 在文件中二分查找满足 sj=ti j ,连 i j 的边,再按算法三的方法找环统计答案即可。时间复杂度 O(nlogn),期望得分100分。

题外话

找满足 sj=ti j 这一步可以用hash,不过要注意模数不能设太小,否则有hash冲突的危险。

另外,和NOIP2015 D1T2现场有人写了Tarjan一样,这题也有人写了Tarjan……你们在写的时候难道不觉得大材小用了吗……

以及这题的测试点11、validator、std一开始都是有问题的,很对不起各位选手TAT……现已修正。


本题难度与NOIP2013 D1T2相近。

100分:10人

50分以上:19人

30分:13人

5分:22人

算法一

对于测试点1,n=1,只需判断 h1 的生命值是否能承受唯一怪物的伤害,即 h_1>a_1b_1 即可。

期望得分5分。

算法二

使用搜索算法。DFS枚举每次打哪个怪物,记录当前已经打败了哪些怪物和剩余生命值。

时间复杂度 O(n!m) ,可以通过前6个测试点获得30分。

如果不会写DFS,可以手动枚举所有的排列情况通过 n3 的15分。

算法三

显然这个搜索是可以记忆化的,考虑使用状态压缩DP。

定义 f(S,h) 表示已消灭怪物集合为 S ,剩余生命值为 h 时,最多还能击败多少怪物,则

f(S,h)=max{f(S{i},h(ai+d|S|)(bi+d|S|)}
其中 iS h-(a_i+d|S|)(b_i+d|S|)>0。当不存在这样的 i f(S,h)=0

做完DP之后,对于每个猎人 j 可以直接查询其最多能消灭的怪物数 f(,hj)。时间复杂度 O(2nnmax{hi}+m) ,期望得分40~45分。

算法四

上述状压DP还可以进一步优化。你会发现 h 这一维范围很大,然而 f(S,h) 范围很小,不妨把两者交换一下!

定义 f(S,k) 表示已消灭怪物集合为 S ,之后消灭 k 个怪物至少需要损失的生命,则

f(S,k)=maxiS{f(S{i},k1)+(ai+d|S|)(bi+d|S|)}
这样查询猎人 j 能消灭的怪物数就是求最大的 k 使得 f(\varnothing,k) < h_i

时间复杂度 O(2nn+mn) ,期望得分50~55分。

算法五

对于测试点16,17, d=0 ,也就是消灭怪物是顺序无关的。

由于消灭怪物 i 消耗的生命值为 aibi,我们将怪物按照 aibi 从小到大排序,那么对于每个猎人,如果能消灭 k 个怪物,那么一定是贪心取 aibi 最小的 k 个怪物。

排序后预处理 aibi 的前缀和,对于每个猎人,可以使用二分法求出其最多能消灭的怪物数量。

时间复杂度 O((n+m)logn) ,期望得分15分。结合上述算法期望得分60~65分。

算法六

d=0 的数据可以贪心,那么 d0 的数据是否也有贪心性质呢?

假设 i,j 是猎人的打怪顺序中两个相邻的怪物, i j 前面,不妨设在打到 i 之前,i 的攻击力和防御力为 ai,bi j 的攻击力和防御力为 aj,bj,则消灭 i,j 总共损失生命 h=aibi+(aj+d)(bj+d)

如果交换 i,j 顺序,即先消灭 j ,再消灭 i,那么总共损失生命 h=ajbj+(ai+d)(bi+d)

由于 hh=(ai+bi)d(aj+bj)d ,当 ai+biaj+bj 时, hh ,也就是交换 i,j 顺序更优。

因此我们有如下结论:一定存在最优解,使得怪物按照 ai+bi 从大到小的顺序消灭。

这样,将怪物按照 ai+bi 从大到小排序,那么答案序列是这样排序后的一个子序列。

用DFS枚举子序列,并更新答案。复杂度 O(2nm) ,期望得分55~60分,结合算法五期望得分65~70分。

算法七

上述算法的DFS枚举子序列同样是可以记忆化的。将怪物按照 ai+bi 从大到小排序后,考虑DP,记 f(i,j,h) 为已经确定前 i 个怪物是否消灭,前 i 个怪物消灭了 j 个,剩余生命 h,之后最多能消灭多少个怪物,则

f(i,j,h)=0,f(i+1,j,h),max{f(i+1,j,h),f(i+1,j+1,h(ai+1+dj)(bi+1+dj))+1},amp;iamp;h(ai+1+dj)(bi+1+dj)0amp;otherwisegt;n

DP之后,每个猎人 j 最多消灭怪物数可以直接查询 f(0,0,hj)。时间复杂度 O(n2max{hi}+m) ,期望得分55分,结合算法五、六期望得分80分。

算法八

最后,仿照算法四对算法三的优化,将上述DP进行优化就是正解了。

同样将怪物按照 ai+bi 从大到小排序,然后记 f(i,j) 为前 i 个怪物消灭 j 个,至少消耗多少生命值,则

f(i,j)=0,+,min{f(i1,j),f(i1,j1)+[ai+d(j1)][bi+d(j1)]},amp;j=0,amp;jamp;0gt;i,lt;ji

DP之后,对每个猎人 j 可以使用二分法求出其最多消灭的怪物数量。

最后我们成功地解决了这个问题,时间复杂度 O(n2+mlogn),期望得分100分。

题外话

我看到不少人直接把怪物按照 aibi 从小到大排序然后对每个人 O(n) 贪心,然后喜闻乐见地只得了5分。这样写连样例二都过不去2333 所以大家都知道这样做是错的吧,不过从得分情况来看得5分的比得30分的还要多很多。我想说既然知道错为啥还有这么多人不改QAQ 暴搜都有30分啊。论错误算法的危害性。

事实上这题的DP可以用数据结构优化到 O(nlogn) ,不过这部分超过了NOIP难度范围,所以并没有出出来……感兴趣的同学可以自行研究。


魔术帽游戏

本题难度与NOIP2012 D1T3相近。

100分:4人

52分以上:18人

24分:35人

算法一

每局游戏暴力模拟交换过程,根据交换的魔术帽编号推出魔术球被交换到的位置。

时间复杂度 O(mq) ,期望得分24分。

算法二

对于所有 xj=lj=1 的数据,相当于询问“如果球一开始放在魔术帽 1 内,然后从第 1 个操作往下做,求 ri 次操作后球的位置”。

于是设球一开始位于魔术帽 1 内,然后从头到尾模拟一遍整个操作序列,每次交换以后记录下当前球的位置,就能对所有的 r 预处理出 r 次操作后球的位置了,询问就可以直接回答了。

时间复杂度 O(m+q),期望得分12分,结合算法一期望得分36分。

算法三

对于 n=2 的数据,交换只有两种,一种是 12,21 ,即有效的交换,另一种是不变。

显然后一种交换不用考虑。如果在第 i 局游戏中,有效的出现了偶数次,那么球的位置不变,仍为 xi,如果出现了奇数次,那么球的位置改变,为 3xi

因此问题转化为统计区间 [li,ri] 内有多少个有效的交换,即有多少个 j[li,ri] 满足 ajbj

数据结构学傻选手可能会想用线段树或者树状数组维护。由于只有查询,只需前缀和即可解决这个问题:设 Si 为有多少个 j[1,i] 满足 ajbj ,那么 [li,ri] 内有效交换的次数就是 SriSli1 ,球的位置改变的条件是 Sli1 Sri 奇偶性不同。

时间复杂度 O(m+q) ,期望得分16分,结合算法一、二期望得分52分。

算法四

我们尝试一些其他的做法,比如线段树。

考虑用线段树维护操作序列,对于每个线段树结点 [l,r] 开一个大小为 n 的数组 a,其中 a[i] 表示如果当前球位于 i 号魔术帽内,依次进行区间 [l,r] 内的交换之后求将会到什么位置。

这样的信息不难维护:对于非叶结点的数组 a ,其左、右儿子的数组分别为 a1,a2,那么 a[i]=a2[a1[i]]

询问时可以将查询区间定位到 O(logm) 个结点上,用线段树维护的信息可以求得答案,就可以 O(logm) 高效地求得答案。

遗憾的是建线段树需要 O(mn) 的时间和空间复杂度,因此只能通过 mn 不大的数据,期望得分64分,结合算法二期望得分76分。

算法五

线段树的做法似乎没有什么前途,我们换一种思路。

建立一个 (m+1)n 个结点的图,每个结点为一个二元组 (i,j) 1im+1 1jn ,表示时刻 i 魔术球在魔术帽 j 中。对于每个操作 i ,连边 (i,ai)(i+1,bi) (i,bi)(i+1,ai) 以及 (i,j)(i+1,j) j{ai,bi} ),这样每个结点的出度不超过 1

那么每局游戏 i 的答案就是从点 (li,xi) 出发一直走,走到第一个满足 y>r_i 的点 (y,z) z 的值。

例如样例一:

5 5 3
2 4
1 4
1 5
3 4
1 2
2 1 3
1 2 4
2 3 5

对应的图如下:

(1,1)---(2,1)\  /(3,1)\   /(4,1)--(5,1)\/(6,1)
(1,2)\ /(2,2)-\/-(3,2)-\-/-(4,2)--(5,2)/\(6,2)
(1,3)-*-(2,3)-/\-(3,3)--*--(4,3)\/(5,3)--(6,3)
(1,4)/ \(2,4)/  \(3,4)-/-\-(4,4)/\(5,4)--(6,4)
(1,5)---(2,5)----(3,5)/   \(4,5)--(5,5)--(6,5)

对于第 1 局游戏,从 (1,2) 出发走到的第一个 y>r_i 的点 (y,z) (4,5) ,因此第 1 局游戏的答案为 5

直接模拟仍然是暴力的复杂度,不过可以用倍增法优化。对每个点 (i,j) 和非负整数 klog2m ,预处理 f(i,j,k) 为从点 (i,j) 出发走 2k 步到达的点,例如上面样例的 f(1,2,2)=(5,5)

这样,求 (i,j) 出发的第一个 y>r 的点 (y,z) ,可以从大到小枚举 k=log2m,log2m1,,1,0 ,对每个 k ,如果 f(i,j,k) 超过了 r 则不变,否则把 (i,j) 跳转至 f(i,j,k) 。最后 (i,j) 的后继即为目标。

这样每局游戏的答案就可以 O(logm) 求出了。然而这样倍增预处理的时间和空间复杂度为 O(mnlogm) ,仍然只能通过 mn 不大的数据,期望得分与算法四相同。

算法六

我们发现算法五的图中绝大多数边都是 (i,j)(i+1,j) 的,十分浪费。考虑对每个 i=1,2,,n 只建 (i,ai),(i,bi) 两个点,共 2n 个点。

对于每个点 (i,ai) (i,bi) ,将其连向魔术球在第 i 次交换后位于 bi(或 ai )之后,下一次参与的交换操作。形式化地:

  • 对于每个 (i,ai) ,将其连向 (j,bi) ,其中 j 是满足 j>i bi{aj,bj} 的最小整数(如果有);

  • 对于每个 (i,bi) ,将其连向 (j,ai) ,其中 j 是满足 j>i ai{aj,bj} 的最小整数(如果有)。

例如上面的样例对应的图如下:

        (2,1)-\  /-(3,1)    /-------(5,1)
(1,2)\ /-------\/----------/--------(5,2)
      X        /\         /  (4,3)
(1,4)/ \(2,4)-/  \-------/---(4,4)
                   (3,5)/

求第 i 局游戏的答案,先找出最小的整数 j[li,ri] 使得点 (j,xi) 存在,如果存在这样的 j ,之后和算法五一样,用倍增法优化求出最后一次参与的交换,进而得到答案;否则第 i 局游戏中魔术球不会参与任何交换,答案为 xi

现在唯一的问题是如何对多个 (i,x) 找出最小的 j>i 使得点 (j,x) 存在,即 x{aj,bj} 。只有解决了这一步才能完成建图和求解游戏。

维护一个大小为 n ,初值为 + 的数组 L ,然后从大到小枚举 i=n,n1,,2,1,对每个 i

  • L[ai] L[bi] 修改为 i ,这样,L[x] 就是最小的 ji 使得点 (j,x) 存在;

    • 建图方面,连边 (i,ai)(L[bi],bi) (如果 L[bi]+ )和 (i,bi)(L[ai],ai) (如果 L[ai]+ );

    • 求解方面,取出所有满足 lj=i 的游戏局 j ,如果 L[xj]rj,则从点 (L[xj],xj) 开始,用算法五的倍增法求出第 j 局的答案,否则第 j 局的答案为 xj

    • “取出所有满足 lj=i 的游戏局 j ”这一步比较简单的实现方法是对每个 i 开一个链表存储满足 lj=i 的所有 j

      时间复杂度 O(n+(m+q)logm),期望得分100分。

      算法七

      本人在出这道题的时候想到算法六,就把它当作标算出出来了……后来给其他同学做发现了更优秀的算法。

      注意到在算法六中,建出来的图实际上是若干条链,因此倍增法的实质是在链上二分查找大于 ri 的最小的数。

      而这个二分是可以省掉的,我们只需把游戏按 ri 从小到大排序( ri 不超过 m ,因此可以对每个 x=1,2,,m 开一个链表存储满足 ri=x i ,以完成排序),那么在递增数列 C 上,随着 ri 的递增, C 中大于 ri 的最小值不下降。

      于是可以依次取出图中每一条链 C 以及操作在 C 上的所有游戏局(同样用链表存储),然后按 x 从小到大枚举 C 中的点 (x,y) y{ax,bx} ),由于 ri 已经排好序,故可从前往后把每局游戏 i 的答案设为 y,然后把 i 扔掉,直到 rix 为止。

      时间复杂度 O(n+m+q) ,期望得分100分。

      实现时不需要逐条链取出来,可以直接在算法五建的图中同时模拟这 n <script type="math/tex" id="MathJax-Element-3245">n</script> 条链,具体实现方式这里不再赘述,大家可以自行思考。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值