zoukankan      html  css  js  c++  java
  • 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 该如何安排使用加速器,才能使所有乘客的旅行时间总和最小?

    输入输出格式

    输入第 1 行是3个整数 n, m, k,每两个整数之间用一个空格隔开。分别表示景点数、乘客数和氮气加速器个数。 
    第 2 行是 n-1 个整数,每两个整数之间用一个空格隔开,第 i 个数表示从第 i 个景点开往第 i+1 个景点所需要的时间,即 Di。 
    第 3 行至 m+2 行每行3 个整数 Ti, Ai, Bi,每两个整数之间用一个空格隔开。第 i+2 行表示第 i 位乘客来到出发景点的时刻,出发的景点编号和到达的景点编号。输出共一行,包含一个整数,表示最小的总旅行时间。

    对于 100%的数据,1 ≤ n ≤ 1,000,1 ≤ m ≤ 10,000,0 ≤ k ≤ 100,000,0 ≤ Di ≤ 100,0 ≤ Ti≤ 100,000。

    题解:

    稍微想一想,发现,

    不能二分(怎么验证合法与否??),不能dp(没有最优子结构性质)

    不能模拟(要有决策啊),数据结构好像也不是很像,而且就算是有,也就是一个辅助维护功能罢了。

    所以看起来似乎就剩下了贪心??!?!

    但是左思右想,不知道怎么个贪心。。。。

    这是一道非常神的贪心题目了。

    首先有一个基础的事实:

    发现,如果车到达某个点,不论早还是晚,至少要在这个点的所有的人都上去以后才能走,所以至少是这个点最后一个人到达的时间

    所以,似乎可以从这里入手。

    是不是只要是到了这里,如果到这里的时间,会早于这个点最后一个人到达的时间,

    最后这条边就不用加速了呢?

    貌似有点道理,因为早到晚到都是要等的么。

    但是其实是错的!!

    因为,对于一些人会在这一站下车,他们不用等到最后一个人上车啊。

    如果有很多人在车上,并且都在这一站下车,那么加速一下可能还是很有必要的

    (所以很多题解都是错的)

    但是,不要放弃,还可以抢救一下。

    这个题就是用一种决策,使得总的时间最小

    猜想:是不是每次找一个能减少最多总时间的路,减一下?

    那么怎么找在i~i+1的路加速一下,会减少多少总时间呢?

    其实就是这次加速,能影响到最远到哪一站下车的人。

    这个影响范围种影响的人数,就是这次加速减少的总时间。

    以下部分摘自:https://blog.csdn.net/qq_37220238/article/details/80059161

    假设在第i站使用加速器:

        ①假设观光车到达i+1站的时间小于以i+1站为起点的乘客到达i+1站最晚的时间,说明该观光车到达i+1站之后要等待这位乘客到达之后才会出发;若在i站使用加速器,观光车到达i+1站的时间会变短,使在i+1站下车的乘客的花费时间变短,但是观光车到达i+1站之后仍然要等待这位最晚到达的乘客上车之后才能出发,所以这个加速器的最大有效范围只到i+1站。

        ②假设观光车到达i+1的时间等于以i+1站为起点的乘客到达i+1站最晚的时间,说明该观光车到达i+1站之后等无需等待,直接出发;若在i站使用加速器,观光车到达i+1站的时间会变短,使在i+1站下车的乘客的花费时间变短,但是观光车到达i+1站之后仍然要等待这位最晚到达的乘客上车之后才能出发,所以这个加速器的最大有效范围只到i+1站。

        ③假设观光车到达i+1的时间大于以i+1站为起点的乘客到达i+1站最晚的时间,说明观光车到达i+1车站之后无需等待,直接出发;若在i站使用加速器,则加速器的效果会延续到之后的若干站,则在第i站使用加速器的最大有效范围等于在第i+1站使用加速器的最大有效范围。

    代码中,讨论即可。

    虽然不太明白影响范围的含义,但是请先往下看

    求出这个影响范围,好像还不能直接确定影响多少人啊。

    发现,在i加速,假设影响范围边界在j(i小于j)那么,在i+1~j下车的乘客,都可以获得加速。

    显然,i以及之前下车的乘客已经毫无关系了。

    至于j+1之后的下车的乘客。。。。。

    因为到达j之后,加速已经不能影响到后面了。

    说明,这次加速之后,一定到达j的时间,比j最后一个人到达的时间要晚!!!

    也就是说,到达j之后,目的地在j+1之后的乘客,只能继续等待。不论再怎么加速,还是没有用!

    所以,这一部分也不会节省总时间。

    所以,就是i+1~j区间下车乘客的人数了。(期间必须在车上,j以及之前,必须下车)

    用一个前缀和+差分可以搞定。

    首先,预处理下车的前缀和,预处理每个点最后一个上车的人的时间,

    找出不用加速时的:到每个点时间,每条边长度。

    然后,while(k)

    每次k--,循环找出影响范围(每次可能不同),找出节省的时间,选择最多的,把长度-1,最后更新到达每个点的时间。

    如果不能节省时间了,就break结束好了。

    复杂度理论O(nk)实际情况很快(数据水么)

    返回来,刚才那个猜想:“是不是每次找一个能减少最多总时间的路,减一下?”

     为什么是对的?

    我们先看一下代码:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1005;
    int n,m,k;
    int rang[N],d[N],las[N],sum[N];//影响范围,i~i+1长度,i点最后人,前缀和 
    int st[10*N],nd[10*N],tim[10*N];//每个人 
    int go[N];//到i时间 
    int tot;//总时间 
    int main(){
        scanf("%d%d%d",&n,&m,&k);
        for(int i=1;i<=n-1;i++){
            scanf("%d",&d[i]);
        }
        for(int i=1;i<=m;i++){
            scanf("%d%d%d",&tim[i],&st[i],&nd[i]);
            las[st[i]]=max(las[st[i]],tim[i]);
            sum[nd[i]]++;
        }
        go[1]=0;
        for(int i=2;i<=n;i++) sum[i]+=sum[i-1],go[i]=max(go[i-1],las[i-1])+d[i-1];
        for(int i=1;i<=m;i++){
            tot+=go[nd[i]]-tim[i];
        }
        while(k){
            k--;
            rang[n-1]=n;
            for(int i=n-2;i>=1;i--){//更新影响范围 
                if(go[i+1]<=las[i+1]){
                    rang[i]=i+1;
                }
                else rang[i]=rang[i+1];
            }
            int mx=0,id=0;
            for(int i=1;i<=n-1;i++){//找最优解 
                int now=sum[rang[i]]-sum[i];
                if(mx<now&&d[i]){
                    mx=now,id=i;
                }
            }
            //这里可以特判mx==0 break 但是其实没什么优化 
            tot-=mx;d[id]--;
            for(int i=id+1;i<=rang[i];i++) //最后进行修改,注意循环的范围 
            go[i]=max(go[i-1],las[i-1])+d[i-1];
        }
        printf("%d",tot);
        return 0;
    } 

    下面进行证明:

    1.首先,对于两个边,决定加速的先后顺序是没有关系的。

    但是,我们这个算法,由于每次会进行修改go,rang,导致一个路径可能之前可以优化,但是现在不能优化了。

    猜想的可怀疑性在于:修改了当前的最优解,可能导致许多比较大的优化,都变小了。

    2.为什么会导致这种情况呢?因为有的路径变短了,到达i+1的时间和las可能从大于变成等于了。

    在id处进行一次操作,所有的都会变吗?

    不是这样的。

    其实,这种变化本质上引起的是go的变化。

    注意到,最后更新go的循环范围可以小到从i+1~rang(i)

    i的go肯定不会变,i之前go也不会变。

    rang之后的go也不会变,因为根据rang的定义,rang+1会卡住车的。不能继续前进下去。go(rang+1)的max肯定是从las(rang)过来的。

    所以,影响的就是i+1到rang的go了。

    3.这时,考虑交换法证明贪心。

    ①对于范围p,q(设起点分别为i,j,i!=j,其中i是当前的最优解id位置) p比q减去的时间更多,并且p减去了,q就不能取到原来的rang(q)了的时候:

    根据上面的推论,q一定有一部分包含在i+1~rang(i)中,否则变化p不会影响q

    而这里,p是一个当前极大的贡献,

    那么,rang(i-1)一定就是i,否则i-1的贡献一定比i大。所以,j的左端点,不小于i,否则范围不会有i+1到rang(i)的部分。

    并且,q的右端点rang(j)一定等于rang(i),否则,rang(i)或者rang(j)一定能走得更远!!

    所以,现在我们证明了,q一定包含于p,并且p,q右端点重合,

    所以,当且仅当,i<j,rang(i)=rang(j)时,两者会互相影响。

    那么,先选择p,一定已经包含了选择q的贡献了!!

    反过来,如果选择在q点加速。。。。

    如果改了p,rang(q)会被影响,那么改了q,rang(p)其实也一定会影响。(因为肯定是q+1~rang(q)的一些点go改变了)

    所以,既然会互相影响,而且又不会影响到别的rang,

    选择了q(即j)想要达到p的贡献,要之后再选择一次i的。但是就白白浪费了一次加速机会。不优。

    ②其他情况,一定都是p,q不会互相影响的。对于先后处理互不影响的,取最大的减去肯定不劣。

    证毕。Q.E.D

  • 相关阅读:
    串口速率问题
    java中byte short int的理解
    matlab 设置坐标轴的有效数字
    Tomcat闪退
    解决linux下sudo更改文件权限报错xxxis not in the sudoers file. This incident will be reported.
    Git的常用命令
    [原创]创建指定RowState属性的DataRow实例
    [转]中文VS2008中安装ASP.NET MVC框架出现问题的解决方法
    [转]JavaScript为事件处理器传递参数
    [转]LINQ to SQL(LINQ2SQL) vs. ADO.NET Entity Framework
  • 原文地址:https://www.cnblogs.com/Miracevin/p/9578551.html
Copyright © 2011-2022 走看看