题目链接
题目大意 对于数组S,每次出发开始可以选择任意元素作为起始点,然后在数组上移动,落点为i + S[i],直至超出数组范围,每次经过的点的值减一(先移动再减/直至减到1为止),求使数组元素全为1所用最少的出发次数
数据范围 数组大小n:1<=n<=5000 元素大小:1<=a[i]<=1e9
思路 首先考虑到数组从前向后的第一个非1元素,这类元素如果不是从他开始那么他将永远不会减一,因此每一次弹跳的起始点选择都应该是第一个非1元素,然后只需要模拟弹跳过程,同时记录数组中1的个数,当1的个数和数组大小相同时,说明数组里面已经全是1了,那么就只要输出此时的次数就行了。
但是这么做,显而易见的,就t了,因为如果每次跳过的距离很短,而且数组还开的很大的话,执行的次数就会过多了。
而优化的方法就考虑贪心的做法,尽量的减少没必要的跳跃过程,如当第一个非1元素保存的S[i]过于大,导致i+S[i]的结果超出了n,那么就可以将i+S[i]-n次的超界操作省略,直接修改S[i]的大小并改变cnt(用于保存出发了几次)的大小。另外如出发后经过S[i]值为1的位置时,我们可以直接跳到下一个非1元素,因为经过1的时候每次都是一步一步走,一定不会漏掉任何一个元素,而且1也不会改变,所以直接跳过是没有问题的。
修改的过程中也出现了一点问题,就是我构造nxt数组来存储处于i位置的元素的next指针,而我考虑了好一会怎么维护1元素的next指针,即指向下一个非1元素,我觉得每次维护都用遍历的话可能会有n2的复杂度,会不会还是t,后来想想完全是多虑,数组长度总共只有5000,就算是n2,也就2e6完全不用担心t。
#include<iostream>
#include<cstdio>
using namespace std;
int t,n,numOne,firNoOne,pos,lastNoOne,lastOne;
long long S[5005];
long long cnt;
struct node//用于记录每个非一结点的前一个非一结点和后一个非一结点的序号
{
int last = 0;
int next = 0;
}nxt[5005];
int main()
{
scanf("%d",&t);
while(t--)
{
cnt = 0;
numOne = 0;
firNoOne = 0;
scanf("%d",&n);
for(int i = 1;i <= n;i++)
{
scanf("%lld",&S[i]);
if(S[i]==1)//在输入的时候就先记录1出现过的次数
numOne++;
else if(S[i]!=1&&firNoOne == 0)//第一个非一结点
{
firNoOne = i;
lastNoOne = i;
}else if(S[i]!=1)//后续非一结点
{
nxt[lastNoOne].next = i;
nxt[i].last = lastNoOne;
lastNoOne = i;
}
}
nxt[lastNoOne].next = n+1;//最后一个非一结点的下一个一个非一结点记为n+1 方便查到的时候直接跳出
lastOne = n+1;
for(int i = n;i >= 1;i--)//更新所有一结点的next
{
if(S[i]!=1) lastOne = i;
else nxt[i].next = lastOne;
}
while(numOne!=n)//开始模拟
{
if(S[firNoOne]==1)//先判定此时第一个非一标记标记的位置的S值是否已经到1
{
firNoOne = nxt[firNoOne].next;
}
pos = firNoOne;
if(S[pos]+pos > n&&pos != n)//特殊情况 第一个弹床就直接把人弹出去 直接减掉 少进几次循环 减到弹到最后一张蹦床的情况
{
cnt += S[pos] + pos - n;
S[pos] = n - pos;
if(S[pos]==1) numOne++;//特殊情况 倒数第二的时候如果直接减掉 S会只剩下1 这个时候numOne也要加一下
continue;
}
//但是还有一种更特殊的情况 就是从最后一个蹦床开始跳 无论S是几他都会跳出去 而且最后加的cnt会多加1 因为他不能从自己跳到自己 那就再多写一个特殊情况
if(pos == n)//而且这种情况也没必要进入下面的while循环 因为他每次就跳一下 那就直接贪心
{
cnt += S[pos]-1;
numOne++;
continue;
}
cnt++;
while(pos <= n)//进入跳一跳环节
{
if(S[pos]!=1) lastNoOne = pos;//跳到非一结点 标记一下 因为如果再下一次跳到的是一结点 就可以直接飞到下一个非一结点
else if(S[pos] == 1)//跳之前就是1的话就直接飞到下一个非一
{
pos = nxt[pos].next;
continue;
}
S[pos]--;
if(S[pos] == 1)//跳完之后变成1的话 需要更新一下nxt数组
{
numOne++;
if(pos!=firNoOne)
{
nxt[nxt[pos].last].next = nxt[pos].next;
if(nxt[pos].next!=n+1) //如果是最后一个非一结点 就不需要更新后一个结点的last 也不需要更新next
{
nxt[nxt[pos].next].last = nxt[pos].last;
for(int j = pos;S[j]==1;j--)
{
nxt[j].next = (S[j+1] == 1)? nxt[j+1].next:j+1;
}
}
}
}
pos = pos+S[pos]+1;
}
}
printf("%lld\n",cnt);
}
return 0;
}
第一次写博客题解,算是记录一下我acm的开始,之后打了训练赛之后的题解也尽量保持写博客的习惯,望坚持。