P3613 【深基15.例2】寄包柜

P3613 【深基15.例2】寄包柜

题目描述

超市里有 \(n(1\le n\le10^5)\) 个寄包柜。每个寄包柜格子数量不一,第 \(i\) 个寄包柜有 \(a_i(0\le a_i\le10^5)\) 个格子,不过我们并不知道各个 \(a_i\) 的值。对于每个寄包柜,格子编号从 1 开始,一直到 \(a_i\)。现在有 \(q(1 \le q\le10^5)\) 次操作:

  • 1 i j k:在第 \(i\) 个柜子的第 \(j\) 个格子存入物品 \(k(0\le k\le 10^9)\)。当 \(k=0\) 时说明清空该格子。
  • 2 i j:查询第 \(i\) 个柜子的第 \(j\) 个格子中的物品是什么,保证查询的柜子有存过东西。

已知超市里共计不会超过 \(10^7\) 个寄包格子,\(a_i\) 是确定然而未知的,但是保证一定不小于该柜子存物品请求的格子编号的最大值。当然也有可能某些寄包柜中一个格子都没有。

输入格式

第一行 2 个整数 \(n\)\(q\),寄包柜个数和询问次数。

接下来 \(q\) 个行,每行有若干个整数,表示一次操作。

输出格式

对于查询操作时,输出答案,以换行隔开。

输入输出样例 #1

输入 #1

5 4
1 3 10000 118014
1 1 1 1
2 3 10000
2 1 1

输出 #1

118014
1

说明/提示

\(\text{upd 2022.7.26}\):新增加一组 Hack 数据。

题解

方案一:使用动态数组 vector

vector 的嵌套来模拟一个动态的二维数组,核心是使用 resize 按需分配空间。它可以完美平衡空间与效率,存取都是 O(1) 的,能稳定 AC。

#include <bits/stdc++.h>
using namespace std;

struct Box {
    // 核心思路:用 vector 的嵌套来模拟一个动态的二维数组。
    // cabinets[i] 代表第 i 个柜子,它是一个一维的 vector<int>,用来存储该柜子每个格子的物品编号。
    vector< vector<int> > cabinets; 
    
    // 参数化列表:直接用 cabinets(n + 1) 初始化
    Box(int n) : cabinets(n + 1) {}

    // 存入物品
    void storeItems(int i, int j, int k) {
        // 如果当前柜子的格子数(size)不够 j+1,就动态扩容。
        if (cabinets[i].size() < j + 1) {
            cabinets[i].resize(j + 1);
        }
        cabinets[i][j] = k;
    }

    // 查询物品
    int queryItem(int i, int j) {
        // 根据题目保证,查询的格子一定存过东西,所以直接返回即可。
        return cabinets[i][j];
    }
};

int main() {
    ios::sync_with_stdio(false); // 加快输入输出
    cin.tie(nullptr);

    int n, q;
    cin >> n >> q;
    
    Box box(n); // 创建一个管理 n 个柜子的 Box 对象

    while (q--) {
        int op, i, j, k;
        cin >> op;
        if (op == 1) {
            // 存入操作
            cin >> i >> j >> k;
            box.storeItems(i, j, k);
        } else {
            // 查询操作
            cin >> i >> j;
            cout << box.queryItem(i, j) << '\n'; // 注意换行
        }
    }

    return 0;
}

代码细节

  • 构造函数 Box(int n):接收柜子总数 n,并用 resize(n + 1) 初始化外层 vector。多一个位置是为了让柜子编号 i 从 1 开始,处理起来更自然。
  • storeItems 函数:在存放物品前,先检查 cabinets[i].size() < j + 1。如果为真,说明格子不够,就调用 resize(j + 1) 动态扩容,然后再执行 cabinets[i][j] = k 存入。
  • queryItem 函数:根据题目保证,查询的格子一定存过东西,所以可以直接返回 cabinets[i][j]

方案二:使用映射 map (更灵活)

如果你想要一个更“随用随取”的方案,map 是不二之选。它只存储真正用到的格子,内存利用率最高,存取效率是 O(log n) 级别,同样能 AC。

#include <bits/stdc++.h>
using namespace std;

struct Box {
    // 核心思路:使用 map 的嵌套来模拟一个稀疏的二维数组。
    // 只存储真正有物品的格子,非常节省内存。
    map<int, map<int, int> > storage;

    // 构造函数,这里不需要做特别的初始化操作。
    Box() = default;

    // 存入物品
    void storeItems(int i, int j, int k) {
        storage[i][j] = k;
    }

    // 查询物品
    int queryItem(int i, int j) {
        return storage[i][j];
    }
};

// main 函数部分和上面完全一样

代码细节

  • 数据结构 storagemap<int, map<int, int>>,外层键是柜子编号 i,内层键是格子编号 j,值就是物品 k
  • storeItems 函数:直接 storage[i][j] = k 即可,map 会自动处理键值对的创建与更新。
  • queryItem 函数:直接 return storage[i][j],同样由 map 自动处理。

方案对比

特性 方案一 (vector) 方案二 (map)
核心数据结构 vector<vector<int>> (动态二维数组) map<int, map<int, int>> (映射嵌套)
内存占用 仅分配实际使用的空间,利用率高 精确存储,极高的内存利用率
存取效率 O(1) O(log n)
适用场景 推荐。网格访问模式,需要极致性能 极度稀疏,或需要灵活处理键值关系

笔记

Box() = default; 的意思是:显式要求编译器为这个结构体生成一个默认(无参)构造函数,并且这个构造函数的行为和编译器“原本会自动生成的一模一样”。

具体做了以下几件事:

  1. 生成默认构造函数
    对于 Box 这个结构体,编译器会合成一个形如 Box() {} 的构造函数。因为你显式写了 = default,这个行为就被明确地保留下来。

  2. 初始化成员变量
    执行这个默认构造函数时,会按照成员在类中的声明顺序,依次调用它们自身的默认构造函数。
    在你的例子中,Box 有一个成员:

    map<int, map<int, int>> storage;
    

    因此 Box() 会调用 storage 的默认构造函数,从而得到一个空的 map

  3. 保证“无参构造”始终可用
    在 C++ 中,只要你在类里定义了任何一个自定义构造函数(比如 Box(int n)),编译器就会停止自动生成默认构造函数。此时如果你想保留 Box b; 这种无参写法,就必须自己提供,而 = default 就是最简洁的方式——相当于告诉编译器:“我虽然可能会加别的构造函数,但默认的那个请帮我保留好”。

在你的“方案二”中,map 不需要预先知道柜子总数,所以使用 Box box;(无参)即可,因此添加 Box() = default; 恰好满足了这一需求,让对象能够用最自然的方式创建。

posted @ 2026-05-11 23:14  炸酱面配小米酒  阅读(34)  评论(0)    收藏  举报