前天做了下美团的一个codeM比赛的资格赛,遇到一个题目挺有意思的,所以现在做一下总结。

题目描述

美团的每一个用户都有一个用户代金券的消费记录日志,每位用户都能购买若干种代金券,但是每一种代金券最多只能购买一张。如果现在手上已经有了一张某种的代金券,那么只有在消费完这张代金券之后才能再次购买同种代金券。每位用户的购买和消费记录都记录在日志文件中,字符I代表购买代金券,字符O代表消费代金券。现在由于系统出现故障,造成了日志文件的损坏,在这个文件中有些记录无法恢复,只能用字符?代替,现在需要你来检查这个日志文件中的记录是否是合理的,如果出现某些冲突则表示该日志文件出现错误。
例如在某位用户的消费记录日志中的记录是这样的:

I 1
O 1

代表用户首先购买了编号为1的代金券,然后又消费了该代金券,所以该日志文件是正确的。对于正确的日志文件我们输出-1。
如果消费记录是这样的:

I 1
I 1
O 1

因为用户重复购买了编号为1的代金券,这是不合理的,所以这个日志文件有问题,问题出现在第二行,所以我们应该输出2。
如果消费记录是这样的:

?
I 1
O 2

因为这个?是无法恢复的,所以可以看做是任何可能的记录,在这个例子中我们可以将它视为I 2,在这样的解释之下,这个日志文件是合理的,所以我们最终的输出是-1。

输入样例

首先我们需要输入一个m,表明这个日志文件的总的行数。之后就像上述的例子那样的输入。

2
I 1
O 1
3
I 1
I 1
O 1
3
?
I 1
O 2
4
I 1
?
O 1
O 1

输出样例

如果日志文件没有问题那么输出-1,如果有问题则输出最早出现问题的行数。

-1
2
-1
4

解题思路

当时没有想明白具体的逻辑,改了很久都没有过,今天问小崔才明白正确的解题思路。经过分析之后我们知道,输入?的时候是肯定不会有错误的,错误只能出现在IO的某行。那么当输入为I number的时候我们需要检查我们是否已经拥有这张代金券,如果没有那么这条记录是没有问题的,如果已经购买了这张代金券我们就应该进一步的检查,看上一次编号为number的代金券是什么时候买的,如果在这之间有一个?那么我们可以将这个?作为一个O number的记录,这样也就不会产生错误了。同样当输入为O number的时候我们同样按照相同的思路进行处理,如果有问题我们就应该进一步检查上一个相同编号的消费记录在什么时候,在这段时间内是否有?,如果有?我们就可以进行合理解释,否则就是一个错误的消费记录。

解题代码

#include <cstdio>
#include <set>
#include <cstring>
using namespace std;
const int maxn = 100000+10;

int m, number;
char type;
int buy_time[maxn], sale_time[maxn];
bool coupon[maxn];
set<int> unsure;
set<int>::iterator idx;
int main() {
    while (~scanf("%d", &m)) {
        memset(buy_time, -1, sizeof(buy_time));
        memset(sale_time, -1, sizeof(sale_time));
        memset(coupon, false, sizeof(coupon));
        unsure.clear();
        int ans = -1;
        for (int i = 1; i <= m; ++i) {
            getchar();
            scanf("%c", &type);
            if (type == '?') {
                unsure.insert(i);
            } else {
                scanf("%d", &number);
                if (type == 'I') {
                    if (!coupon[number]) {
                        coupon[number] = true;
                    } else {
                        // 对处于上一个I与现在这个I之间的第一个?做一个处理,将它作为一个O使用
                        int preBuyTime = buy_time[number];
                        idx = unsure.lower_bound(preBuyTime); // 返回preBuyTime之后的第一个?的位置
                        if (idx != unsure.end()) {
                            unsure.erase(idx);
                        } else {
                            // 如果没有?可以用,那么就是错误情况了
                            if (ans == -1) {
                                ans = i;
                            }
                        }
                    }
                    buy_time[number] = i;
                } else {
                    if (coupon[number]) {
                        coupon[number] = false;
                    } else {
                        // 对处于上一个O与现在这个O之间的第一个?做一个处理,将它作为一个I使用
                        int preSaleTime = sale_time[number];
                        idx = unsure.lower_bound(preSaleTime); // 返回preSaleTime之后的第一个?的位置
                        if (idx != unsure.end()) {
                            unsure.erase(idx);
                        } else {
                            // 如果没有?可以用,那么就是错误情况了
                            if (ans == -1) {
                                ans = i;
                            }
                        }
                    }
                    sale_time[number] = i;
                }
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

Focus on Tech & Enjoy Life!