复赛四:小熊的果篮

洛谷:P7912
OJ:P4973

解法一:70分

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**************************************************************** 
 * Description: 2021年CSP-J 复赛第四题,小熊胡果篮(fruit)
   主要思路是利用一个标记 flag 来记录当前被挑选的水果种类,并在挑选完一个果篮后更新该标记,
   以确保每次挑选时不会重复挑选同一块中的水果。
   该算法的时间复杂度是𝑂(𝑛^2)
 * Author: Alex Li
 * Date: 2023-10-16 14:33:09
 * LastEditTime: 2024-08-05 20:22:13
****************************************************************/
#include <iostream>
using namespace std;

// 定义全局数组 a,用于存储每个水果的种类,n 表示水果的数量,cnt 用于记录已经挑出的水果数量
int a[200010], n, cnt;

int main() {
    // 读取水果的数量
    cin >> n;
    
    // 读取每个水果的种类,1 代表苹果,0 代表桔子
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    
    // 当已经挑出的水果数量少于总数量时,继续挑选
    while (cnt < n) {
        int flag = 2; // 初始 flag 值设置为 2,表示当前未选中的水果种类(1 或 0)
        
        // 遍历所有水果
        for (int i = 1; i <= n; i++) {
            // 如果当前水果还未被挑出且其种类与上一次选中的不同
            if (a[i] != -1 && a[i] != flag) {
                flag = a[i]; // 更新 flag 为当前水果的种类
                a[i] = -1; // 将当前水果标记为已挑出
                cout << i << ' '; // 输出当前水果的编号
                cnt++; // 已挑出的水果数量加一
            }
        }
        
        // 输出当前果篮的所有水果编号后换行
        cout << endl;
    }
}

解法二:80分
采用队列,以数据块为单位处理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**************************************************************** 
 * Description: 2021 CSP-J 复赛第4题,小熊的果蓝
 利用结构体存储单个块,并用队列存储所有块
 * Author: Alex Li
 * Date: 2023-10-16 14:55:45
 * LastEditTime: 2024-08-05 20:37:10
****************************************************************/
#include <iostream>
#include <queue>
using namespace std;

// 定义节点结构体,用于存储块的左右边界和块的水果种类
struct node {
    int L, R, num;
};

int n, t, cnt;
queue<node> q; // 定义队列用于存储所有块

int main() {
    int flag = 2; // 初始 flag 值设置为 2,表示当前未选中的水果种类(1 或 0)
    cin >> n;
    
    // 读取每个水果的种类,并根据块的定义将块加入队列
    for (int i = 1; i <= n; i++) {
        cin >> t;
        if (t != flag) {
            q.push((node){i, i, t}); // 如果当前水果与前一个不同,则新建一个块
            flag = t;
        } else {
            q.back().R = i; // 如果相同,则延伸当前块的右边界
        }
    }
    
    // 当队列不为空时,进行挑选操作
    while (!q.empty()) {
        int k = q.size(); // 记录当前队列的大小,即当前的块数
        int flag = 2; // 重置 flag
        
        // 遍历当前所有块
        for (int i = 1; i <= k; i++) {
            // 如果当前块的水果种类与上一次选中的不同
            if (q.front().num != flag) {
                cout << q.front().L << ' '; // 输出块的最左边水果编号
                q.front().L++; // 将块的左边界右移
                flag = q.front().num; // 更新 flag 为当前块的种类
            }
            
            // 如果块的左边界未超过右边界,将块重新入队
            if (q.front().L <= q.front().R)
                q.push(q.front());
            q.pop(); // 移除当前块
        }
        
        // 输出当前果篮的所有水果编号后换行
        cout << endl;
    }
}

解法三: 合并队列(满分)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/**************************************************************** 
 * Description: 2021 CSP-J 复赛第四题,小熊的果篮
 两个队列,合并队列
 * Author: Alex Li
 * Date: 2023-10-16 14:55:45
 * LaLRitTime: 2023-10-17 05:44:49
****************************************************************/
#include <algorithm>
#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

struct block { // 定义块结构体
    int L, R, th; // L: 块的左边界, R: 块的右边界, th: 块的水果种类
} f, x, ad;

int n, cnt, t[200001];
queue<block> q, q2; // 定义两个队列,一个用于处理块,一个用于合并块
bool used[200001]; // 记录每个位置的水果是否已被取出

int main() {
    cin >> n;
    
    // 读取每个水果的种类
    for (int i = 1; i <= n; i++) {
        cin >> t[i];
    }
    
    t[n + 1] = !t[n]; // 设置一个哨兵,用于标记最后一个块的结束
    
    // 合并相同水果的块
    for (int i = 2, si = 1; i <= n + 1; i++) {
        if (t[i] != t[i - 1]) {
            q.push((block){si, i - 1, t[i - 1]}); // 将块推入队列
            si = i; // 更新下一个块的起始位置
        }
    }
    
    cnt = n; // 初始化剩余水果数量
    
    while (cnt) {
        while (q.size()) {
            f = q.front(); // 取出队列中的第一个块
            q.pop();
            
            // 移动块的左边界,跳过已取出的水果
            while (used[f.L] && f.L <= f.R) {
                f.L++;
            }
            
            if (f.L > f.R) continue; // 如果块已经空了,跳过
            
            cout << f.L << ' '; // 输出当前块的第一个水果编号
            cnt--; // 减少剩余水果数量
            used[f.L] = 1; // 标记当前水果已取出
            
            if (f.R == f.L) continue; // 如果这个块只有一个水果,继续
            
            f.L++; // 移动块的左边界
            q2.push(f); // 将块存入临时队列 q2
        }
        
        cout << endl;
        
        // 合并相邻相同种类的块
        while (q2.size()) {
            ad = q2.front();
            q2.pop();
            
            while (q2.size()) {
                x = q2.front();
                if (x.th == ad.th) { // 如果相邻块的种类相同,合并块
                    ad.R = x.R;
                    q2.pop();
                } else {
                    break;
                }
            }
            
            q.push(ad); // 将合并后的块推入主队列
        }
    }
}

解法四:链表(满分)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/**************************************************************** 
 * Description: 2021 CSP-J 复赛第四题目 链表的方式
 * Author: Alex Li
 * Date: 2024-08-05 20:44:27
 * LastEditTime: 2024-08-05 22:53:13
****************************************************************/
#include <iostream>
#include <cstdio>
#include <vector>

using namespace std;

const int MAXN = 2e5+10; // 定义最大水果数量

struct Node {
    int prev; // 前一个节点的索引
    int val;  // 当前节点的值
    int next; // 后一个节点的索引
};

int n; // 水果数量

int fruit[MAXN]; // 用于存储每个水果的种类
Node headList[MAXN]; // 用于存储块的头节点, val 字段表示水果的位置
Node fruitList[MAXN]; // 用于存储所有水果的双向链表节点,val表示块的头位置。
int cc; // 用于记录当前块的数量

// 函数用于取出一个水果
void EatOne(int pos) { //pos=position 位置
    int prev = fruitList[pos].prev;
    int next = fruitList[pos].next;
    fruitList[prev].next = next; // 更新前一个节点的 next 指向
    fruitList[next].prev = prev; // 更新后一个节点的 prev 指向
    cout<<pos<<' '; // 打印当前取出的水果位置
}

// 函数用于删除一个块的头节点
void Del(int pos) {
    int prev = headList[pos].prev;
    int next = headList[pos].next;
    headList[prev].next = next; // 更新前一个头节点的 next 指向
    headList[next].prev = prev; // 更新后一个头节点的 prev 指向
}

// 主函数用于处理每次取水果操作
void getFruit() {
    int solo = headList[0].next; // 从第一个块开始
    int nowcolor = fruit[headList[solo].val]; // 当前块的水果种类
    while (solo != cc + 1) { // 遍历所有块
        if (fruit[headList[solo].val] != nowcolor) { // 如果当前块的水果种类不同
            Del(solo); // 删除当前块的头节点
            solo = headList[solo].next; // 移动到下一个块
            continue;
        }
        EatOne(headList[solo].val); // 取出当前块的一个水果
        headList[solo].val = fruitList[headList[solo].val].next; // 更新块的头节点
        if (fruit[headList[solo].val] != nowcolor) { // 如果更新后的块的水果种类不同
            Del(solo); // 删除当前块的头节点
        }
        solo = headList[solo].next; // 移动到下一个块
        nowcolor ^= 1; // 切换水果种类(0 和 1 之间切换)
    }
    cout<<'\n'; // 打印换行
}

int main() {
    cin>>n; // 读取水果数量
    fruitList[0].next = 1; // 初始化双向链表的起点
    for (int i = 1; i <= n; i++) { //从1开始
        cin>>fruit[i]; // 读取每个水果的种类
        fruitList[i].prev = i - 1; // 初始化前一个节点的索引
        fruitList[i].next = i + 1; // 初始化后一个节点的索引
    }
    fruit[0] = 2; // 设置哨兵值
    fruit[n+1] = 2; // 设置哨兵值
    headList[0].next = 1; // 初始化头节点链表的起点
    for (int i = 1; i <= n; i++) {
        if (fruit[i] != fruit[i - 1]) { // 如果当前水果种类与前一个不同
            cc++; // 增加块的数量
            headList[cc].prev = cc - 1; // 初始化前一个块的索引
            headList[cc].next = cc + 1; // 初始化下一个块的索引
            headList[cc].val = i; // 初始化当前块的值
        }
    }
    while (fruitList[0].next != n + 1) { // 当还有水果未取出时
        getFruit(); // 进行一次取水果操作
    }
    return 0;
}
Scroll to Top