题目链接:https://ac.nowcoder.com/acm/contest/634/B
这题是我喜欢的类型——没有复杂的数据结构和算法,只需认真思考,我行,你也行。
思路:没有复杂高深的算法,只需对暴力法优化。
分析:
一.拿到题目,先看数据范围,分析时间复杂度
n的范围是2e7,似乎连O(n)的算法都可能TLE。不过注意到,数据的读入就需要O(n),所以O(n)的算法肯定是可行的。
二.先思考Brute force
这题的Brute force做法显而易见:模拟。双重循环。外循环枚举每个线段i,内循环计算从线段i的0位置到线段n的步数。求和即得答案。不过,在没有优化的情况下,时间复杂度是O(n^2),显然是无法AC的。
三.思考优化方法
既然Brute force是没办法AC的,而这题又似乎也没办法用什么高大上的算法做,我们就只能思考如何优化Brute force。下面我就介绍一下我的思考过程。
1.哪里可以优化?
a.递推是否可行?
由于我们横下心不用什么数据结构和算法,所以优化的方法就比较少了。这一题看上去似乎可以递推,那我们就认真分析一下。
我们模仿数学上的证明过程,先来设置一个引理:当能在O(1)的时间得到从线段i的0位置往下走,第一次被卡住的线段编号,那么我们就能在O(n)的时间用递推得出答案。
很玄学对吧。没关系,我们慢慢证明。令kz[i](卡住ka zhu)表示从线段i的0位置往下走,第一次被卡住的线段编号,根据引理,我们已经将kz数组求出来了。那么,我们就可以递推了。
我们注意到题目中的条件:如果第i+1条线段长度不到j+1,那么就会回到第i条线段的0位置,然后继续跳。也就是说,从第i条线段的0位置往下跳,跳到kz[i]被卡住了,需要返回kz[i]-1行的0位置继续跳。那么从第i条的0位置跳到第n条线段的代价比从kz[i-1]行的0位置跳到第n行的代价多出了kz[i]-i,这个是可以在O(1)的时间求出来的。所以,第i行的代价可以转化为第kz[i-1]行的代价,只不过从第kx[i-1]行的0位置到第n行的代价在求的时候要加倍。我们可以用一个cnt数组来记录遍历到这一行时有几个点同时往下走。所以我们只需一次遍历,就能求出来答案,时间复杂度为O(n)。
理解不了?没关系,配合下面的图,理解起来就方便多了。
b.引理如何优化?
虽然推出来了上一步里的递推关系,但是别高兴的太早:上一步的成立是建立在引理成立的基础上。也就是说,我们同样需要在O(n)的时间里求出kz数组。这一步的方法和证明就比较有难度了,希望各位能耐心看下去。
对于这张图,我们要证明三个结论:
(1)对于任意m,i
(2)对于任意k,i
下面证明:
(1)由于kz[i]表示i第一次需要回退一行的编号,所以对于任意m属于(i,j),a[m]>=m-i;所以对于任意t属于(i,m),a[m]>m-t;所以t肯定不被m卡住。对于所以的m,m不被m到j-1的行卡住,即kz[m]>=j。证毕。
(2)由于kz[i]=j,则a[j]
(3)由于a[j]>j-p,又由(1)知,kz[p]>j,所以kz[p]>j;
现在,证明完毕,我也可以介绍我的做法了。
首先,我们将kz数组赋值为无穷大,从i=nxt到n遍历一遍,nxt初始为1,维护下次开始遍历的位置。每次从nxt开始往下找到第一个会卡住的位置j。则从任意k属于[nxt,j-a[j]-1],kz[k]=j;nxt=j-a[j];
然后,再一次倒序遍历,若kz[i]!=无穷大,则将所有i到下一个kz[j]!=无穷大的j,任意k属于[j,i],kz[k]=kz[i];即可得kz数组。
至此,两个优化均完成,且总时间复杂度为O(n),最后提醒的是,注意用long long int 记录答案。于是,AC!
代码如下:
#include
#include
#include
#include
#include
#include