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
改成什么名称。虽然改名可以改成任意字符串,但我们只关心是改成
这样可以通过
n
比较小的数据。由于这些数据的文件名都是一个数字,可以用大小为 bool
数组判断文件名是否冲突。
该算法视实现方式优劣,期望得分30至50分。
算法二
对于满足特殊性质2的数据,我们发现,把每个文件名直接改成 ti 一定不会冲突。
因此去掉 si=ti 的文件之后,剩下的文件都只需要改一次名,这样次数显然是最少的。输出剩下的文件名个数即可。
期望得分35分,结合算法一期望得分50至60分。
算法三
算法二什么时候能产生反例呢?我们发现,如果文件
i
存在一个文件
同样先删去满足
si=ti
的文件,然后对每个文件
i
,若存在文件
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 -----------
对于不在环上(即在链上,如上面的
对于在环上的文件(如上面的
1,2
以及
5,6,7
),则要先将某个文件
i
的文件名改成一个随机串(这样指向
因此找环统计即可求得答案。找环只需要从每个未被标记的点
i
开始,不断访问
如果暴力建图,即对每个
i
枚举满足
结合一些特殊数据可以得到更多分数。例如:
对于测试点15,16,
si,ti
都是值不小于
106
的正整数,所以可以开一个大小为
106
的数组,对每个数
x
存满足
对于剩余数据,按照算法二输出剩余文件个数,可以通过测试点17,期望得分85分。
算法四
对于一般数据,同样容易处理。
由于文件名不长,每个文件名可以转换成一个不超过10位的64进制数,大小不超过
将文件按
si
排序,之后对每个点
i
在文件中二分查找满足
题外话
找满足 sj=ti 的 j 这一步可以用hash,不过要注意模数不能设太小,否则有hash冲突的危险。
另外,和NOIP2015 D1T2现场有人写了Tarjan一样,这题也有人写了Tarjan……你们在写的时候难道不觉得大材小用了吗……
以及这题的测试点11、validator、std一开始都是有问题的,很对不起各位选手TAT……现已修正。
本题难度与NOIP2013 D1T2相近。
100分:10人
50分以上:19人
30分:13人
5分:22人
算法一
对于测试点1,
期望得分5分。
算法二
使用搜索算法。DFS枚举每次打哪个怪物,记录当前已经打败了哪些怪物和剩余生命值。
时间复杂度 O(n!m) ,可以通过前6个测试点获得30分。
如果不会写DFS,可以手动枚举所有的排列情况通过 n≤3 的15分。
算法三
显然这个搜索是可以记忆化的,考虑使用状态压缩DP。
定义
f(S,h)
表示已消灭怪物集合为
S
,剩余生命值为
做完DP之后,对于每个猎人
j
可以直接查询其最多能消灭的怪物数
算法四
上述状压DP还可以进一步优化。你会发现
h
这一维范围很大,然而
定义
f(S,k)
表示已消灭怪物集合为
S
,之后消灭
时间复杂度 O(2nn+mn) ,期望得分50~55分。
算法五
对于测试点16,17, d=0 ,也就是消灭怪物是顺序无关的。
由于消灭怪物
i
消耗的生命值为
排序后预处理
时间复杂度 O((n+m)logn) ,期望得分15分。结合上述算法期望得分60~65分。
算法六
d=0 的数据可以贪心,那么 d≠0 的数据是否也有贪心性质呢?
假设
i,j
是猎人的打怪顺序中两个相邻的怪物,
i
在
如果交换
i,j
顺序,即先消灭
j
,再消灭
由于 h′−h=(ai+bi)d−(aj+bj)d ,当 ai+bi≤aj+bj 时, h′≤h ,也就是交换 i,j 顺序更优。
因此我们有如下结论:一定存在最优解,使得怪物按照 ai+bi 从大到小的顺序消灭。
这样,将怪物按照 ai+bi 从大到小排序,那么答案序列是这样排序后的一个子序列。
用DFS枚举子序列,并更新答案。复杂度 O(2nm) ,期望得分55~60分,结合算法五期望得分65~70分。
算法七
上述算法的DFS枚举子序列同样是可以记忆化的。将怪物按照
ai+bi
从大到小排序后,考虑DP,记
f(i,j,h)
为已经确定前
i
个怪物是否消灭,前
DP之后,每个猎人
j
最多消灭怪物数可以直接查询
算法八
最后,仿照算法四对算法三的优化,将上述DP进行优化就是正解了。
同样将怪物按照
ai+bi
从大到小排序,然后记
f(i,j)
为前
i
个怪物消灭
DP之后,对每个猎人 j 可以使用二分法求出其最多消灭的怪物数量。
最后我们成功地解决了这个问题,时间复杂度
题外话
我看到不少人直接把怪物按照 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
内,然后从头到尾模拟一遍整个操作序列,每次交换以后记录下当前球的位置,就能对所有的
时间复杂度
算法三
对于 n=2 的数据,交换只有两种,一种是 1→2,2→1 ,即有效的交换,另一种是不变。
显然后一种交换不用考虑。如果在第
i
局游戏中,有效的出现了偶数次,那么球的位置不变,仍为
因此问题转化为统计区间 [li,ri] 内有多少个有效的交换,即有多少个 j∈[li,ri] 满足 aj≠bj 。
数据结构学傻选手可能会想用线段树或者树状数组维护。由于只有查询,只需前缀和即可解决这个问题:设 Si 为有多少个 j∈[1,i] 满足 aj≠bj ,那么 [li,ri] 内有效交换的次数就是 Sri−Sli−1 ,球的位置改变的条件是 Sli−1 和 Sri 奇偶性不同。
时间复杂度 O(m+q) ,期望得分16分,结合算法一、二期望得分52分。
算法四
我们尝试一些其他的做法,比如线段树。
考虑用线段树维护操作序列,对于每个线段树结点
[l,r]
开一个大小为
n
的数组
这样的信息不难维护:对于非叶结点的数组
a
,其左、右儿子的数组分别为
询问时可以将查询区间定位到 O(logm) 个结点上,用线段树维护的信息可以求得答案,就可以 O(logm) 高效地求得答案。
遗憾的是建线段树需要 O(mn) 的时间和空间复杂度,因此只能通过 mn 不大的数据,期望得分64分,结合算法二期望得分76分。
算法五
线段树的做法似乎没有什么前途,我们换一种思路。
建立一个
(m+1)n
个结点的图,每个结点为一个二元组
(i,j)
,
1≤i≤m+1
,
1≤j≤n
,表示时刻
i
魔术球在魔术帽
那么每局游戏
例如样例一:
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)
对于第
直接模拟仍然是暴力的复杂度,不过可以用倍增法优化。对每个点 (i,j) 和非负整数 k≤⌊log2m⌋ ,预处理 f(i,j,k) 为从点 (i,j) 出发走 2k 步到达的点,例如上面样例的 f(1,2,2)=(5,5) 。
这样,求
(i,j)
出发的第一个 y>r 的点
(y,z)
,可以从大到小枚举
k=⌊log2m⌋,⌊log2m⌋−1,⋯,1,0
,对每个
k
,如果
这样每局游戏的答案就可以 O(logm) 求出了。然而这样倍增预处理的时间和空间复杂度为 O(mnlogm) ,仍然只能通过 mn 不大的数据,期望得分与算法四相同。
算法六
我们发现算法五的图中绝大多数边都是 (i,j)→(i+1,j) 的,十分浪费。考虑对每个 i=1,2,⋯,n 只建 (i,ai),(i,bi) 两个点,共 2n 个点。
对于每个点
(i,ai)
或
(i,bi)
,将其连向魔术球在第
i
次交换后位于
对于每个 (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
局游戏的答案,先找出最小的整数
现在唯一的问题是如何对多个 (i,x) 找出最小的 j>i 使得点 (j,x) 存在,即 x∈{aj,bj} 。只有解决了这一步才能完成建图和求解游戏。
维护一个大小为
n
,初值为
将
L[ai] 和 L[bi] 修改为 i ,这样,L[x] 就是最小的 j≥i 使得点 (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 扔掉,直到ri≥x 为止。时间复杂度 O(n+m+q) ,期望得分100分。
实现时不需要逐条链取出来,可以直接在算法五建的图中同时模拟这 n <script type="math/tex" id="MathJax-Element-3245">n</script> 条链,具体实现方式这里不再赘述,大家可以自行思考。