Codeforces Global Round 13 C. Pekora and Trampoline

Codeforces Global Round 13 C. Pekora and Trampoline

原题传送门

题目大意

告诉你公园里有n个蹦床,每个蹦床有一个能量值Si, 当你跳上第i个蹦床后,你会跳向第i+Si 个蹦床,并且Si 会减1如果它大于1的话,但是如果i+Si大于n,那么就会停止这趟奇妙的旅行,否则,他将继续按照这个规则跳下去,直到停止,然后题目问你的是求将所有的Si 变成1所需要的最少的旅行次数

分析:这题因为数据较小,所以O(n2 )也能过,这里给出一个O(n)的解法,不是dsu,那个我不会,具体用差分来做。

1.首先我们来贪心♂的考虑这件事情。注意到我们是为了把所有的Si 变成1,对于已经变成1的如果我们跳上去,只会一个一个的向右侧移动,直到遇到第一个不为1的Si 。我们来考虑如果跳上一个蹦床对整体产生的贡献,显然,它会让本身的Si 减1,并且让它路径上所有的Sj>1的点减1;这些点一定在它的右侧,所以Si 对整体产生的贡献一定都在该点本身以及向右的位置,如果跳出界的话就只有对本身的贡献。这样我们就很清楚的知道,如果一个Si >1并且在i的前面不存在点的能量值大于1,则对该点的减小操作有且仅能从该点出发,执行Si -1次操作。

2.我们知道,一旦我们跳上一个蹦床,它后面的路径就已经被确定了,换句话说,一个路径可以由该路径的起始点来表示。这个很显然。接着我们再继续1的思考。对于第一个Si >1的i来讲,这Si -1次操作会让它从自身出发跳到的点的位置逐渐偏左。第一次从Si 出发跳到的第一个点是Si +i,第二次则是Si +i-1…最后一次便是Si +2。如果他的Si 已经为1就没必要继续操作了,所以最后一次一定是在Si +2。那么该点所需要的最小操作次数为Si -1次。

3.那么问题来了,我们要如何记录这些信息,又要如何处理呢。如果我们按照上述操作,第一个Si 处理完毕后,如果在这个Si 后面还有大于1的点(从Si 出发的路径对整体的贡献先咕咕咕),我们就要分析从Si 出发的所有路径中是否有点被路径覆盖,如果没有,我们就继续1,2的操作,如果有,我们就必须记录并处理这些信息。我们在2中已经分析过,一个路径可以由一个起始点来表示,那么Si 产生的路径便是从Si+2一直到Si +i-1。我们给这些点打上一个标记,考虑到是区间,所以我们很自然的就能想到利用差分数组,我们维护一个差分数组d,两端打上标记,如果越界就打到n+1。

这样我们在从左往右遍历点的时候,利用差分求前缀和,就知道目前点白嫖的旅行有多少次。如果目前白嫖的旅行次数大于等于了Sj ,那么这个点一定已经变成了1,对于溢出的部分就让它向右走,因为它只能跳一格了,它已经跳不动了。这样给后面两个点再打上差分标记。

但是如果白嫖的不够厉害,我自己还要付出钱去旅行(即这时候又回到1中的情况),重复上述操作即可。但是可能会有人会问,如果我只是给每个点扩撒得第一个点打上标记,我又不知道他之后能跳几格,毕竟是在后面得先跳,可能会跳到重复的点。不,这没有任何关系,我们从左往右遍历的时候,一定能知道当前点的白嫖次数,这就足够求出,它本身能跳到的下一个的点的集合,因为Sj只能一点一点的变小,每次只能减1,即使后面的点先跳,我们只要知道那个点到底白嫖了几次,就足以求出每个点必要的旅行费用。

如果我们直接分析一个点,假设它前面的点都已经处理完毕了,那么当前的点的被前面所覆盖的次数,便是白嫖次数,对于当前点的每一次旅行,它的下一个点总是确定的,所以我们只需要搜集一个左侧的信息,就一定能推出当前点的信息,从而推至右侧。

萌新(没有萌新实力的菜鸡)第一次发题解,写的逻辑不是很好,求各位大佬照顾,轻喷,主要是为了巩固而用的。

以下是代码

#include <iostream>
#include<stdio.h>
#include<algorithm>

using namespace std;
typedef long long ll;
const int N = 1e5+10;
ll t,n,m,q,u,v;
ll s[N],d[N];
void solve(){
    cin>>n;
    for(int i = 1;i <= n;++i){cin>>s[i];d[i] = 0;}
    ll ans = 0;
    for(int i = 1;i <= n;++i){
        d[i] += d[i-1];
        ++d[i+2],--d[min(n,s[i]+i)+1];
        if(d[i] >= s[i])d[i+1] += d[i] - s[i] + 1, d[i+2] -= d[i] - s[i] + 1;
        s[i] = max(s[i]-d[i],1ll);
        ans += s[i] - 1;
    }
    cout<<ans<<endl;
}

int main() {
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    cin>>t;

    while(t--){
        solve();
    }

    fclose(stdin);
    fclose(stdout);
    return 0;
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值