【贪心】NOIP2011观光公交详解

【题目描述】
风景迷人的小城Y市,拥有n个美丽的景点。由于慕名而来的游客越来越多,Y市特意安排了一辆观光公交车,为游客提供更便捷的交通服务。观光公交车在第0分钟出现在1号景点,随后依次前往2、3、4……n号景点。从第i号景点开到第i+1号景点需要Di分钟。任意时刻,公交车只能往前开,或在景点处等待。

设共有m个游客,每位游客需要乘车1次从一个景点到达另一个景点,第i位游客在Ti分钟来到景点Ai,希望乘车前往景点Bi(Ai < Bi)。为了使所有乘客都能顺利到达目的地,公交车在每站都必须等待需要从该景点出发的所有乘客都上车后才能出发开往下一景点。假设乘客上下车不需要时间。

一个乘客的旅行时间,等于他到达目的地的时刻减去他来到出发地的时刻。因为只有一辆观光车,有时候还要停下来等其他乘客,乘客们纷纷抱怨旅行时间太长了。于是聪明的司机ZZ给公交车安装了k个氮气加速器,每使用一个加速器,可以使其中一个Di减1。对于同一个Di可以重复使用加速器,但是必须保证使用后Di大于等于0。

那么ZZ该如何安排使用加速器,才能使所有乘客的旅行时间总和最小?

【问题分析】
考场上想到了是贪心算法,肯定把氮气放在人多的时候用,但是考虑到车要等人之类的因素,觉得无法证明其正确性,所以没有采用,那么现在我们来说明一下这个神奇的正确性 ps 显然大法好。。。

首先我们可以得到这样一个性质,即氮气的使用满足最优子结构。无论有几个氮气加速器,第一个的使用都是一样的,所以我们可以对每个加速器进行单独分析,即可采用贪心策略。那么该如何贪心呢?显然,应该讲氮气使用在公交车上面人数较多的时刻上面。那么这个加速器会对多少人造成影响呢?

公交车到一个站之后,可能需要等人,也可能不需要等人,直接开往下一站。那么如果公交车等人了,那么使用氮气加速后也没有效果,所以加速器的作用区间为从使用的那一站开始一直到某一站公交车需要等人为止,这个区间内的所有旅客都可以受益,所以一个氮气加速器的作用应该是这一段区间的和。

#include<iostream>
#include<cstdio>
using namespace std;
const int N=1001;
int arrive[N],leave[N],bus[N],off[N],sum[N];
//arrive[i] 到达i站的时间 leave[i] 离开i站的时间 
//off[i] i站下车人数 sum[i] 区间和
int n,m,k,ans;
int readin()
{
    int x=0,f=1;  char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void read()
{
    int i,t,a,b;
    n=readin(); m=readin(); k=readin();
    for(i=1;i<n;i++) bus[i]=readin();
    for(i=1;i<=m;i++)
    {
        t=readin(); a=readin(); b=readin();
        off[b]++; leave[a]=max(leave[a],t); ans-=t;
    }
    return;
}
void work()
{
    int i,now,use;
    while(k)
    {
        for(i=1;i<=n;i++)
            arrive[i]=max(arrive[i-1],leave[i-1])+bus[i-1];
        now=0;
        for(i=n;i>1;i--)
        {
        //倒序是为了方便累加sum的和
            if(!bus[i-1])
                sum[i-1]=0;
            else
            {
                sum[i-1]=off[i];
                if(arrive[i]>leave[i])
                    sum[i-1]+=sum[i];
            }
        }
        for(i=1;i<n;i++)
            if(now<sum[i])
            {
                now=sum[i];
                use=i;
            }
        if(!now)    break;
        bus[use]--; k--;
    }
    for(i=1;i<=n;i++)
            arrive[i]=max(arrive[i-1],leave[i-1])+bus[i-1];
    for(i=1;i<=n;i++)
        ans+=arrive[i]*off[i];
    printf("%d",ans);
    return;
}
int main()
{
    freopen("bus.in","r",stdin);
    freopen("bus.out","w",stdout);
    read();
    work();
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
2020年NOIP初赛试题答案详解如下: 1. 小明有6只不同颜色的小球,分别放在6个盒子里。首先,他从第1个盒子取出一个球,放在第2个盒子里;然后,把第2个盒子里的球放在第3个盒子里;接着,将第3个盒子里的球放在第4个盒子里;以此类推,最后将第6个盒子里的球放在第1个盒子里。经过这样的操作后,小明发现每个盒子里的球的颜色都发生了变化,问小明一共进行了多少次操作? 解析:考察循环移位。根据题意可得,第i个盒子的球颜色经过了i次移动。最后小球不变颜色的唯一情况是做了6次循环移位,即每个盒子自己取出了一次球并放回原来的盒子,即可得答案为6。 2. 给定一个长度为n的正整数序列,将序列中的数字分为两个集合A和B,要求集合A的和与集合B的和差的绝对值最小。请问此时集合A中的数字个数最多可能有多少个? 解析:考察动态规划。定义一个二维数dp,dp[i][j]表示前i个数字构成的集合A的和是否等于j。初始dp[0][0]=true,然后遍历数,对于第i个数字nums[i],更新dp[i][j]:若dp[i-1][j-nums[i]]为true,则dp[i][j]也为true。最后从sum/2开始,倒序遍历,找到第一个dp[n][j]=true的j值,即为集合A中的数字个数最多的情况。 3. 给定一个字符串,统计其中出现次数最多的字符的个数,并求出这些字符的ASCII码的平均值。 解析:利用哈希表统计字符出现的次数。遍历字符串,对于每个字符,将其在哈希表中对应的计数加1。然后遍历哈希表,找出计数最大的次数,即为出现次数最多的字符的个数。接着遍历哈希表,计算总和及个数,最后除以个数求平均值,即为字符的ASCII码的平均值。 以上是2020年NOIP初赛试题答案的详细解析。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值