【贪心】观光公交(NOIP2011 day2 T3)

题意:

有n个景点,在横线上依次排列。第i个景点到第i+1个景点的距离为D[i]。现在有m个游客,每个游客会到达一个某个景点A,且正好在第T分钟到达,以及这个乘客要在景点B下车(A < B)。现在有一个公交车,从1号景点按标号依次走到n号景点。每到达一个景点,下车和上车不需要时间,但是必须等到所有的人都上车才能离开。景点之间的消耗的时间等于距离。
现在有k个氮气加速器。用在D[i]上可以使D[i]–(不能减成负数)。现在求一个氮气加速器的使用方案,使得所有人的下车时刻-到达车站的时刻的和最小。
数据范围:
对于 10%的数据,k=0;
对于 20%的数据,k=1;
对于 40%的数据,2 ≤ n ≤ 50,1 ≤ m ≤ 1,000,0 ≤ k ≤ 20,0 ≤ Di ≤ 10,0 ≤ Ti ≤ 500;
对于 60%的数据,1 ≤ n ≤ 100,1 ≤ m ≤ 1,000,0 ≤ k ≤ 100,0 ≤ Di ≤ 100,0 ≤ Ti ≤ 10,000;
对于 100%的数据,1 ≤ n ≤ 1,000,1 ≤ m ≤ 10,000,0 ≤ k ≤ 100,000,0 ≤ Di ≤ 100,0 ≤ Ti ≤ 100,000。

思路:

借鉴于:【NOIP2011 day2】观光公交(作者的写作风格很有趣的…)
如标题所言,这道题是贪心。很容易想到加入一个氮气加速器的时候,影响的人越多越好。这样子就暴力模拟这个过程就好了。
每一次使用一个氮气加速器的时候,从后往前扫。算出所有的位置,如果在这个位置i使用加速器使得D[i]- -,能够影响到的人数。然后选择人数最多的i使用氮气加速器。这里有一个小问题(没有考虑下面的Hack数据的时候,考虑了,改了之后,就不需要考虑这个问题了),就是说,当影响到的人数最多的点不唯一的时候,选最左边的那一个。这是因为,如果是选择靠右的那一个的话,它会使得之前的所有的能够影响到的人数可能会减少,所以说需要选靠左的,可能影响到的人数较少。这样子就得到了一个O(nk)的算法。这样居然能过!!
另外还有一个统计总时间的小技巧:
Ans=mi=1(ed[i]T[i]) A n s = ∑ i = 1 m ( e d [ i ] − T [ i ] )
=mi=1(ed[i])mi=1(T[i]) = ∑ i = 1 m ( e d [ i ] ) − ∑ i = 1 m ( T [ i ] )
=ni=1(out[i]arrive[i])mi=1(T[i]) = ∑ i = 1 n ( o u t [ i ] ∗ a r r i v e [ i ] ) − ∑ i = 1 m ( T [ i ] )
这样子分开统计就好了。
但是还有一个问题(建议看了之后的代码之后再来看这一段),那就是不久前有人在洛谷上面,加入了一个新的hack数据,卡掉了上述所说的贪心,这是链接中没有提到的。仔细分析时候,发现其实当D[i]–到了0的时候,第i个位置所影响的人数应当为0,因为既然当前的位置都不能再使用加速器了,当然就无法影响到任何人了。但是这样子就有个问题,那就是i以前的位置还有可能对i以后的位置的人有影响,但是当peo[i]被清成0之后,就无法去更新之前的状态了。那么就需要先去掉D[i]==0的限制去更新玩所有的状态,然后在选取最大影响人数的判断一下D[i]是否=0即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 1000
using namespace std;
int n,m,k;
int D[MAXN+5],T,A,B;
int maxt[MAXN+5],out[MAXN+5],peo[MAXN+5],arr[MAXN+5];
int main()
{
//  freopen("bus.in","r",stdin);
//  freopen("bus.out","w",stdout);
    scanf("%d %d %d",&n,&m,&k);
    for(int i=1;i<n;i++)
        scanf("%d",&D[i]);
    int ans=0;
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d %d",&T,&A,&B);
        maxt[A]=max(maxt[A],T);//统计每一个点的最晚上车的人
        out[B]++;//统计B点下车的人
        ans-=T;//利用之前的式子
    }
    while(k--)//O(nk)
    {
        memset(peo,0,sizeof(peo));
        for(int i=2;i<=n;i++)
            arr[i]=max(arr[i-1],maxt[i-1])+D[i-1];//求到达时间(不包含等待时间)
        for(int i=n;i>=2;i--)
        {
            peo[i-1]=out[i];//首先累加上第i个点的下车人数
            if(arr[i]>maxt[i])//注意'='是不可以的,因为一旦-1之后,后面仍然是没有影响的
                peo[i-1]+=peo[i];
        }
        int maxval=0,pos=0;
        for(int i=1;i<=n-1;i++)
            if(peo[i]>maxval&&D[i]!=0)//前面所说的特殊处理(最后一段)
            {
                maxval=peo[i];
                pos=i;
            }
        if(maxval==0)
            break;
        D[pos]--;
    }
    for(int i=2;i<=n;i++)
        arr[i]=max(arr[i-1],maxt[i-1])+D[i-1];
    for(int i=1;i<=n;i++)
        ans+=arr[i]*out[i];//公式
    printf("%d\n",ans);
    return 0;
}
/*
Hack数据
4 5 1
3 0 2
0 1 2
0 1 4
0 1 4
0 1 4
0 1 4

*/

如果有不清楚的,可以去参考链接里面的博客。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值