P5661 公交换乘 题解

P5661 公交换乘 题解

原文链接:http://suo.im/6rmWsq

拿到题目看一下,大体思路还是比较清晰的,用一个数组来装所有的收集到的赠票。每当坐地铁的时候,就直接花钱,然后获得一张赠票,放到数组里面。每当坐公交的时候,看看数组里面有没有时间合适,价格小于现在公交票价的赠票,并且没用过的赠票,直接用时间最早的那一张就可以了。

每张赠票要存三个信息,因此用一个结构体来存。数据不超过 \(10^5\),因此开一个 \(10^5\)长度的数组。

const int MAXN = 100005;
struct Ticket {
    //赠票的价格,最晚使用时间和是否需用过
    int price, time, used;
} q[MAXN];//赠票盒子

然而,出题人会这么善良么?显然不会。我们想想极限情况什么样子,极限数据 \(10^5\),假设开始的时候全坐地铁,坐了 \(5*10^4\) 次以后,我们的盒子里面有好多票啊。后面全坐公交,要坐 \(5*10^4\) 次,每次都在这个大盒子里面找合适的票,复杂度 \(O(n^2)\),总计算量 \(2.5*10^9\),我们在超时的边缘疯狂试探啊。

怎么优化呢?关键点在于题中说每次坐车开始时间都不重合,而且45分钟票就过期。所以理论上来讲,盒子里最多也就有45张没过期的票。大量的票都是已经过期了的,没必要从头扫一遍数组,在大量过期的票中浪费宝贵的青春。所以我们想到类似手写队列的方法,定义一个head变量和一个tail变量,分别表示目前还没过期的第一张票的位置,和下一张新的赠票在数组里要插入的位置,每次从head到小于tail循环即可。每次最多循环45次,总共 \(10^5\)次循环,肯定不会超时。

其余细节看代码注释吧。不要复制粘贴哦。

#include <iostream>

using namespace std;
const int MAXN = 100005;
struct Ticket {
    //赠票的价格,最晚使用时间和是否需用过
    int price, time, used;
} q[MAXN];//赠票盒子
int head, tail, n, cost;

int main() {
    cin >> n;
    for (int i = 0; i < n; ++i) {
        int op, price, time;
        //输入每次坐车的种类,价格和发车时间
        cin >> op >> price >> time;
        if (op == 0) {
            //如果是坐地铁,直接把价格加到cost里面
            cost += price;
            //新一张赠票插入数组末尾,这张票的最晚使用时间是当前时间+45
            q[tail].time = time + 45;
            //赠票面额就是地铁票价
            q[tail++].price = price;
        } else {
            //先用一个循环把过期票扔掉
            while (head < tail && q[head].time < time) {
                head++;
            }
            bool found = false;//表示是否有合适的赠票,先假设没有
            for (int j = head; j < tail; ++j) {
                //循环所有剩余的票,这些一定都没过期,因为题目中时间是按顺序给我们的
                if (q[j].price >= price && q[j].used == 0) {
                    //如果价格合适,并且没用过,标记找到了,这张票标记用过
                    found = true;
                    q[j].used = 1;
                    break;
                }
            }
            //如果没找到合适的赠票,老老实实花钱买吧
            if (!found) cost += price;
        }
    }
    cout << cost << endl;
    return 0;
}

题解 P5661 【公交换乘】

原文链接:http://suo.im/5PLR0e

考完pj的我就像一个没有情感的木偶人

正题

我是一个蒟蒻

考场上我调t2用了一个多小时,最后上了个厕所才豁然开朗,发现我把==打成了=(一名蒟蒻的无限牢骚)

ok,牢骚发完了,我来阐述一下我对这道题的理解,望大佬们发现错误请指正。

首先,我一开始拿到这道题的时候毫不犹豫地用了队列,因为这道题的体面在诱导着你用一个动态的数据结构,当 \(t_{bus}\)- \(t_{subway}\) \(>45\)的时候,就pop。

这样想乍一看好像没什么问题,但是随着我继续打下去,才发现并不是我想的那样,因为队列是不可以随机访问任意元素的,它只可以访问队首元素,但是如果遇到队首元素不满足条件但又无法pop的时候,就会出现错误。

比如:

0 7 5
0 12 8
1 8 9

很显然可以看出答案是19,因为第三组可以用第二组的优惠券,但是这时候队列无法访问到第二组数据,如果你说可以把第一组数据pop,那么在第三组数据后面加上这个呢

0 7 5
0 12 8
1 8 9
1 6 13

如果你把第一组pop掉的话,答案很显然就会错误,所以得出结论,此题不能使用队列。

ok,讲完不能使用队列之后,如何处理数据呢,其实我们可以用数组来模拟一个队列,用下标来处理队列的进出情况。

如果是地铁就进,如果不是地铁且不满足条件就出

接下来

code

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
struct Node{
    int price;
    int time;
    int pd;
}a[N];
int n,s;
int main ()
{
    scanf("%d",&n);
    int tail=0,head=1;//想想为什么tail一开始要是0?
    for(register int i=1;i<=n;i++)
    {
        int type,value,t;
        scanf("%d %d %d",&type,&value,&t);
        if(type==0)
        {
            s+=value;
            tail++;
            a[tail].price=value;
            a[tail].time=t;
            a[tail].pd=1;
        }
        else
        {
            while(head<=tail && t-a[head].time>45) head++;
            if(head>tail) s+=value;//此时没有优惠券
            if(head<=tail)
            {
                int ie=1;
                for(register int j=head;j<=tail;j++)
                    if(a[j].price>=value && a[j].pd==1)
                    {
                        ie=-1;
                        a[i].pd=-1;
                        break;  
                    }//这里就是为什么不能用队列的地方
                if(ie==1)
                {
                    s+=value;
                }
            }
        }
    }
    printf("%d",s);
    return 0;
}

码以至此,多说无益,最后希望大家2020csp取得一个好成绩。


有错误的地方请在评论区指出,这篇只是我的个人理解,不保证完全正确。

posted @ 2020-10-15 20:23  SweepyZhou  阅读(494)  评论(0)    收藏  举报