|
|
|
|
## [$AcWing$ $4089$. 小熊的果篮](https://www.acwing.com/problem/content/4092/)
|
|
|
|
|
|
|
|
|
|
### 一、题目描述
|
|
|
|
|
小熊的水果店里摆放着一排 $n$ 个水果。
|
|
|
|
|
|
|
|
|
|
每个水果只可能是苹果或桔子,从左到右依次用正整数 $1$、$2$、$3$、……、$n$ 编号。
|
|
|
|
|
|
|
|
|
|
连续排在一起的同一种水果称为一个 **块**。
|
|
|
|
|
|
|
|
|
|
小熊要把这一排水果挑到若干个果篮里,具体方法是:每次都把 **每一个** **块** 中 **最左边** 的水果同时挑出,组成一个果篮。
|
|
|
|
|
|
|
|
|
|
重复这一操作,直至水果用完。
|
|
|
|
|
|
|
|
|
|
注意,每次挑完一个果篮后,**块** 可能会发生变化。
|
|
|
|
|
|
|
|
|
|
比如两个苹果 **块** 之间的唯一桔子被挑走后,两个苹果 **块** 就变成了一个 **块**。
|
|
|
|
|
|
|
|
|
|
请帮小熊计算每个果篮里包含的水果。
|
|
|
|
|
|
|
|
|
|
**输入格式**
|
|
|
|
|
输入的第一行包含一个正整数 $n$,表示水果的数量。
|
|
|
|
|
|
|
|
|
|
输入的第二行包含 $n$ 个空格分隔的整数,其中第 $i$ 个数表示编号为 $i$ 的水果的种类,$1$ 代表苹果,$0$ 代表桔子。
|
|
|
|
|
|
|
|
|
|
**输出格式**
|
|
|
|
|
输出若干行。
|
|
|
|
|
|
|
|
|
|
第 $i$ 行表示第 $i$ 次挑出的水果组成的果篮。
|
|
|
|
|
|
|
|
|
|
从小到大排序输出该果篮中所有水果的编号,每两个编号之间用一个空格分隔。
|
|
|
|
|
|
|
|
|
|
**数据范围**
|
|
|
|
|
对于 $10$% 的数据,$n$≤$5$。对于 $30$% 的数据,$n$≤$1000$。对于 $70$%
|
|
|
|
|
的数据,$n$≤$50000$。对于 $100$% 的数据,$n$≤$2$×$105$。
|
|
|
|
|
|
|
|
|
|
**输入样例**1:
|
|
|
|
|
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
12
|
|
|
|
|
1 1 0 0 1 1 1 0 1 1 0 0
|
|
|
|
|
```
|
|
|
|
|
**输出样例**1:
|
|
|
|
|
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
1 3 5 8 9 11
|
|
|
|
|
2 4 6 12
|
|
|
|
|
7
|
|
|
|
|
10
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**输入样例**2:
|
|
|
|
|
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
20
|
|
|
|
|
1 1 1 1 0 0 0 1 1 1 0 0 1 0 1 1 0 0 0 0
|
|
|
|
|
```
|
|
|
|
|
**输出样例**2:
|
|
|
|
|
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
1 5 8 11 13 14 15 17
|
|
|
|
|
2 6 9 12 16 18
|
|
|
|
|
3 7 10 19
|
|
|
|
|
4 20
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 二、解题流程
|
|
|
|
|
|
|
|
|
|
#### 1、读题
|
|
|
|
|
- ① 仔细读题,提取关键信息
|
|
|
|
|
- ② 查看测试用例,理解题意
|
|
|
|
|
- ③ 构造 **边界** 测试数据,深入理解题意
|
|
|
|
|
|
|
|
|
|
#### 2、框架
|
|
|
|
|
- ① **正向思考** 主体流程
|
|
|
|
|
- ② 会用到哪些知识点
|
|
|
|
|
- ③ 辅助函数有哪些
|
|
|
|
|
- ④ 以伪代码形式把上面的$3$点写下来
|
|
|
|
|
|
|
|
|
|
#### 3、编码
|
|
|
|
|
- ① 主框架、输入输出完成代码编写,核心部分、调用子函数部分用注释占位
|
|
|
|
|
- ② 对每个子函数完成代码
|
|
|
|
|
|
|
|
|
|
#### 4、测试
|
|
|
|
|
- ① 在$IDE$中完成给定测试用例的测试
|
|
|
|
|
- ② 完成自已构造测试用例的测试
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 三、本题思路
|
|
|
|
|
|
|
|
|
|
依照上面的 解题流程,思考本题:
|
|
|
|
|
|
|
|
|
|
1、块的概念是核心,如果我们能维护好这个块,是不是就能搞定本题呢?
|
|
|
|
|
2、块是一连串的数字,可能是连续的$1$,或者连续的$0$。
|
|
|
|
|
3、首先,这些连续的$1$或者连续的$0$,我们该怎么维护下来的?不维护下来,后面的工作肯定没法做,用什么样的数据结构呢?
|
|
|
|
|
要想一下我们维护的是啥,有啥核心的信息需要记录:
|
|
|
|
|
① 块的开始位置
|
|
|
|
|
② 块的结束位置
|
|
|
|
|
③ 块里装的是啥数字,是$1$还是$0$
|
|
|
|
|
还有吗?好像没有了,那么就用结构体吧:
|
|
|
|
|
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
struct Node { // 块
|
|
|
|
|
int st, ed, th;
|
|
|
|
|
};
|
|
|
|
|
```
|
|
|
|
|
那用了结构体,是不是需要把输入的数据样例转化为这样的形式并且保存下来呢?
|
|
|
|
|
也就是从头开始读取,获取一下当前的数字是啥,然后一直向后走,发现与当前的数字不一样了,就表示换了一个新块,这时,把走完的块记录下来就行了。
|
|
|
|
|
|
|
|
|
|
问题来了,如果我们将数据读取到一个数据$a[]$中,默认的值是$0$,如果我们从头开始到最后结束,用上面的逻辑判断是不是完成了一个新块,那么,在边界上会不会有问题出现呢?
|
|
|
|
|
比如最后一个块,假设它的值是0,那么,由于 $a[]$数组的默认值是$0$,而且,数组一般的处理办法是多开几个,那会不会在编码时造成程序不知道是真的是输入的$0$,还是数组默认的$0$呢?这时如果不处理,就会出问题。如果加上判断,当然也可以解决问题,但更普遍的办法是加上$-1$的默认值,这样,就相当于$n+1$的位置上加上了一个 **哨兵** !使得所有数字的处理逻辑就一样了,代码更短了,不用特判了!
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
memset(a, -1, sizeof a);
|
|
|
|
|
for (int i = 1; i <= n; i++) cin >> a[i];
|
|
|
|
|
```
|
|
|
|
|
那么,如何把块保存下来呢?保存下来放到哪里呢?因为按小熊这么干,它最后肯定会把所有的块合并成一个块,然后不断的取走这个块的第$1$个,同时,由于是每次取走$1$个,所以,它一共需要取$n$次。看来循环$n$次是没跑了
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
while (n)
|
|
|
|
|
```
|
|
|
|
|
至于啥时候$n--$,就看是不是找到了一个块的开始位置,然后输出这个开始位置后才能$n--$。至于用啥保存,这里用的是队列。
|
|
|
|
|
|
|
|
|
|
为啥用队列呢?因为 **队列不空就一直处理,先进来的先处理** 等特点决定,也可以认为是一种经验,这个仔细体会吧,类比下 **边长$3,4,5$的考虑是直角三角形** ~。
|
|
|
|
|
|
|
|
|
|
把整理好的结构体放入队列后怎么处理呢?
|
|
|
|
|
|
|
|
|
|
当然是逐个弹出队列,然后去掉头,如果去完头还有剩余,需要留下来,如果留下来后与其它的块去头后可以连通,那么需要合并块。
|
|
|
|
|
|
|
|
|
|
去头好办,`k.st++`就行,如果没有剩余也好办:`if(k.st>k.ed)`就是没有剩余了
|
|
|
|
|
|
|
|
|
|
下面就遇到了本题的困难点:
|
|
|
|
|
两头大,中间小!
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
这样的测试用例,如果取一次,那么中间$0$的黄色区域就没有了!而新的区间就是$B \sim I$!
|
|
|
|
|
|
|
|
|
|
这就有问题了!啥问题呢?因为如果你这么记录$B \sim I$,你就把一个无用的位置$E$包含进去了,而它却已经不能再取了!!
|
|
|
|
|
|
|
|
|
|
所以,需要一个`used[N]`来记录哪个位置是不是用过了,然后遇到时需要跳过它!
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
那重复整理合并块该怎么做呢?怎么循环啊?
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
while (q2.size()) {
|
|
|
|
|
Node k = q2.front();
|
|
|
|
|
q2.pop();
|
|
|
|
|
while (q2.size()) {
|
|
|
|
|
Node x = q2.front();
|
|
|
|
|
if (x.th == k.th) { // 能合并就合并
|
|
|
|
|
k.ed = x.ed;
|
|
|
|
|
q2.pop();
|
|
|
|
|
} else
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
q1.push(k); // 丢回 q1 里
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
每个块,都向后尽量多的找出与自己数字一样的块,然后合并到,放回到第一个队列中去。
|
|
|
|
|
|
|
|
|
|
### 四、实现代码
|
|
|
|
|
```cpp {.line-numbers}
|
|
|
|
|
#pragma GCC optimize(2) // 累了就吸点氧吧~
|
|
|
|
|
#include <bits/stdc++.h>
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
|
|
const int N = 200010;
|
|
|
|
|
struct Node { // 块
|
|
|
|
|
int st, ed, th;
|
|
|
|
|
};
|
|
|
|
|
int n, a[N];
|
|
|
|
|
queue<Node> q1, q2;
|
|
|
|
|
bool used[N]; // 记录是否被取出
|
|
|
|
|
int main() {
|
|
|
|
|
// 加快读入
|
|
|
|
|
ios::sync_with_stdio(false), cin.tie(0);
|
|
|
|
|
cin >> n;
|
|
|
|
|
memset(a, -1, sizeof a);
|
|
|
|
|
for (int i = 1; i <= n; i++) cin >> a[i];
|
|
|
|
|
|
|
|
|
|
// 很经典的代码
|
|
|
|
|
for (int i = 2, x = 1; i <= n; i++)
|
|
|
|
|
if (a[i] != a[i + 1])
|
|
|
|
|
q1.push({x, i, a[i]}), x = i + 1; // 把连续一段相同的元素合成一个块
|
|
|
|
|
|
|
|
|
|
// 取没拉倒
|
|
|
|
|
while (n) {
|
|
|
|
|
while (q1.size()) { // 有块存在
|
|
|
|
|
Node k = q1.front(); // k这个块
|
|
|
|
|
q1.pop();
|
|
|
|
|
// 1111 00 1111 :取两轮后,第1块和第3块就会连上
|
|
|
|
|
while (used[k.st]) k.st++; // 跳过块中已取过的水果
|
|
|
|
|
if (k.st > k.ed) continue; // 取没了,这个块就放过,看看下一个块吧
|
|
|
|
|
|
|
|
|
|
printf("%d ", k.st); // 如果没有取没,那就取这个号的水果
|
|
|
|
|
n--; // 取走一个水果
|
|
|
|
|
used[k.st] = 1; // 标识已取出
|
|
|
|
|
|
|
|
|
|
k.st++; // 不是最后一个,取完后块的开始位置
|
|
|
|
|
if (k.st > k.ed) continue; // 取没了,这个块就放过,看看下一个块吧
|
|
|
|
|
q2.push(k); // 先将缩减的小块存到 q2 里,因为可能会有些区间连上了,需要进行合并
|
|
|
|
|
}
|
|
|
|
|
puts("");
|
|
|
|
|
|
|
|
|
|
// 1111 00 1111 :取两轮后,第1块和第3块就会连上
|
|
|
|
|
// 完成合并任务
|
|
|
|
|
while (q2.size()) {
|
|
|
|
|
Node k = q2.front();
|
|
|
|
|
q2.pop();
|
|
|
|
|
while (q2.size()) {
|
|
|
|
|
Node x = q2.front();
|
|
|
|
|
if (x.th == k.th) { // 能合并就合并
|
|
|
|
|
k.ed = x.ed;
|
|
|
|
|
q2.pop();
|
|
|
|
|
} else
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
q1.push(k); // 丢回 q1 里
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 五、细节
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|