[CSP-S 2023] 结构体

GitHub跳转原题关系图返回列表

按对齐规则计算类型大小和成员偏移,再递归处理路径访问与地址反查。

OJ: luogu

题目 ID: P9754

难度:普及+/提高

标签:模拟递归哈希表

日期: 2026-07-06 08:46

题意

题目要求模拟一种简化的结构体系统,需要支持四类操作:

  • 定义结构体类型,并输出这个类型的大小和对齐要求;
  • 定义一个全局元素,并输出它的起始地址;
  • 根据 a.b.c 这样的路径访问成员,输出最内层元素的起始地址;
  • 给定一个地址,反查它是否落在某个基本类型元素内部,如果是就输出访问路径,否则输出 ERR

基本类型 byte, short, int, long 的大小和对齐要求分别是 1,2,4,8。结构体类型的对齐要求是所有成员对齐要求的最大值。

思路

这是一道规则模拟题,朴素做法就是按照题意维护类型表、元素表和成员偏移:

cpp
// brute.cpp:小数据朴素模拟,同样按定义顺序计算偏移并递归查找地址。
#include <bits/stdc++.h>
using namespace std;

struct MemberInfo {
    string type_name;
    string name;
    long long offset;
};

struct TypeInfo {
    string name;
    long long size;
    long long align;
    bool is_basic;
    vector<MemberInfo> members;
    map<string, int> member_id;
};

struct ElementInfo {
    string type_name;
    string name;
    long long start;
};

vector<TypeInfo> types;
vector<ElementInfo> elements;
map<string, int> type_id;
map<string, int> element_id;
long long memory_end;

long long align_up(long long x, long long a) {
    if (x % a == 0) return x;
    return x + (a - x % a);
}

void add_basic_type(const string &name, long long size) {
    TypeInfo t;
    t.name = name;
    t.size = size;
    t.align = size;
    t.is_basic = true;
    type_id[name] = (int)types.size();
    types.push_back(t);
}

vector<string> split_path(const string &s) {
    vector<string> result;
    string cur;
    for (int i = 0; i < (int)s.size(); i++) {
        if (s[i] == '.') {
            result.push_back(cur);
            cur.clear();
        } else {
            cur.push_back(s[i]);
        }
    }
    result.push_back(cur);
    return result;
}

bool find_addr_in_type(int tid, long long base, long long addr, const string &path, string &answer) {
    if (types[tid].is_basic) {
        if (base <= addr && addr < base + types[tid].size) {
            answer = path;
            return true;
        }
        return false;
    }
    for (int i = 0; i < (int)types[tid].members.size(); i++) {
        MemberInfo member = types[tid].members[i];
        int child_tid = type_id[member.type_name];
        long long child_base = base + member.offset;
        if (child_base <= addr && addr < child_base + types[child_tid].size) {
            return find_addr_in_type(child_tid, child_base, addr, path + "." + member.name, answer);
        }
    }
    return false;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    add_basic_type("byte", 1);
    add_basic_type("short", 2);
    add_basic_type("int", 4);
    add_basic_type("long", 8);

    int q;
    cin >> q;
    while (q--) {
        int op;
        cin >> op;
        if (op == 1) {
            string name;
            int k;
            cin >> name >> k;

            TypeInfo t;
            t.name = name;
            t.size = 0;
            t.align = 1;
            t.is_basic = false;

            long long cur = 0;
            for (int i = 0; i < k; i++) {
                string type_name, member_name;
                cin >> type_name >> member_name;
                int tid = type_id[type_name];
                cur = align_up(cur, types[tid].align);

                MemberInfo member;
                member.type_name = type_name;
                member.name = member_name;
                member.offset = cur;
                t.member_id[member_name] = (int)t.members.size();
                t.members.push_back(member);

                cur += types[tid].size;
                t.align = max(t.align, types[tid].align);
            }

            t.size = align_up(cur, t.align);
            type_id[name] = (int)types.size();
            types.push_back(t);
            cout << t.size << ' ' << t.align << '\n';
        } else if (op == 2) {
            string type_name, name;
            cin >> type_name >> name;
            int tid = type_id[type_name];

            ElementInfo e;
            e.type_name = type_name;
            e.name = name;
            e.start = align_up(memory_end, types[tid].align);
            memory_end = e.start + types[tid].size;

            element_id[name] = (int)elements.size();
            elements.push_back(e);
            cout << e.start << '\n';
        } else if (op == 3) {
            string path;
            cin >> path;
            vector<string> parts = split_path(path);

            int eid = element_id[parts[0]];
            long long addr = elements[eid].start;
            string cur_type = elements[eid].type_name;
            for (int i = 1; i < (int)parts.size(); i++) {
                int tid = type_id[cur_type];
                int mid = types[tid].member_id[parts[i]];
                MemberInfo member = types[tid].members[mid];
                addr += member.offset;
                cur_type = member.type_name;
            }
            cout << addr << '\n';
        } else {
            long long addr;
            cin >> addr;
            string answer;
            bool ok = false;
            for (int i = 0; i < (int)elements.size(); i++) {
                int tid = type_id[elements[i].type_name];
                long long l = elements[i].start;
                long long r = l + types[tid].size;
                if (l <= addr && addr < r) {
                    ok = find_addr_in_type(tid, l, addr, elements[i].name, answer);
                    break;
                }
            }
            if (ok) cout << answer << '\n';
            else cout << "ERR\n";
        }
    }

    return 0;
}

由于操作数和成员数都不大,直接模拟已经可以作为正解。需要注意的是,结构体内部和全局元素之间都要进行对齐。

定义结构体时,从偏移 0 开始依次放置成员。设当前已经用到的位置为 cur,新成员的对齐要求为 a,那么它的起始偏移是:

text
offset = align_up(cur, a)

放完后令:

text
cur = offset + member_size

所有成员处理完后,结构体大小还要再对齐到结构体自身的对齐要求。

定义全局元素时同理,把当前全局内存末尾 memory_end 对齐到该元素类型的对齐要求,得到元素起始地址。

路径访问 x.ba.ab 时:

  1. 先找到全局元素 x 的起始地址和类型;
  2. 每访问一层成员,就把当前地址加上该成员在当前类型里的偏移;
  3. 当前类型变成该成员的类型。

地址反查要递归进入结构体:

  • 如果当前类型是基本类型,并且地址落在它的范围内,就找到了答案;
  • 如果当前类型是结构体,就检查地址落在哪个成员的范围内,再递归进入这个成员;
  • 如果地址落在结构体对齐留下的空洞里,就输出 ERR

代码

cpp
// main.cpp:模拟结构体类型定义、元素定义、路径访问和地址反查。
#include <bits/stdc++.h>
using namespace std;

struct MemberInfo {
    string type_name;
    string name;
    long long offset;
};

struct TypeInfo {
    string name;
    long long size;
    long long align;
    bool is_basic;
    vector<MemberInfo> members;
    map<string, int> member_id;
};

struct ElementInfo {
    string type_name;
    string name;
    long long start;
};

vector<TypeInfo> types;
vector<ElementInfo> elements;
map<string, int> type_id;
map<string, int> element_id;
long long memory_end;

long long align_up(long long x, long long a) {
    if (x % a == 0) {
        return x;
    }
    return x + (a - x % a);
}

void add_basic_type(const string &name, long long size) {
    TypeInfo t;
    t.name = name;
    t.size = size;
    t.align = size;
    t.is_basic = true;
    type_id[name] = (int)types.size();
    types.push_back(t);
}

vector<string> split_path(const string &s) {
    vector<string> result;
    string cur;
    for (int i = 0; i < (int)s.size(); i++) {
        if (s[i] == '.') {
            result.push_back(cur);
            cur.clear();
        } else {
            cur.push_back(s[i]);
        }
    }
    result.push_back(cur);
    return result;
}

void define_struct_type() {
    string name;
    int k;
    cin >> name >> k;

    TypeInfo t;
    t.name = name;
    t.size = 0;
    t.align = 1;
    t.is_basic = false;

    long long cur = 0;
    for (int i = 0; i < k; i++) {
        string type_name, member_name;
        cin >> type_name >> member_name;
        int tid = type_id[type_name];

        cur = align_up(cur, types[tid].align);

        MemberInfo member;
        member.type_name = type_name;
        member.name = member_name;
        member.offset = cur;
        t.member_id[member_name] = (int)t.members.size();
        t.members.push_back(member);

        cur += types[tid].size;
        t.align = max(t.align, types[tid].align);
    }

    t.size = align_up(cur, t.align);
    type_id[name] = (int)types.size();
    types.push_back(t);

    cout << t.size << ' ' << t.align << '\n';
}

void define_element() {
    string type_name, name;
    cin >> type_name >> name;
    int tid = type_id[type_name];

    ElementInfo e;
    e.type_name = type_name;
    e.name = name;
    e.start = align_up(memory_end, types[tid].align);
    memory_end = e.start + types[tid].size;

    element_id[name] = (int)elements.size();
    elements.push_back(e);

    cout << e.start << '\n';
}

void query_path() {
    string path;
    cin >> path;
    vector<string> parts = split_path(path);

    int eid = element_id[parts[0]];
    long long addr = elements[eid].start;
    string cur_type = elements[eid].type_name;

    for (int i = 1; i < (int)parts.size(); i++) {
        int tid = type_id[cur_type];
        int mid = types[tid].member_id[parts[i]];
        MemberInfo member = types[tid].members[mid];
        addr += member.offset;
        cur_type = member.type_name;
    }

    cout << addr << '\n';
}

bool find_addr_in_type(int tid, long long base, long long addr, const string &path, string &answer) {
    if (types[tid].is_basic) {
        if (base <= addr && addr < base + types[tid].size) {
            answer = path;
            return true;
        }
        return false;
    }

    for (int i = 0; i < (int)types[tid].members.size(); i++) {
        MemberInfo member = types[tid].members[i];
        int child_tid = type_id[member.type_name];
        long long child_base = base + member.offset;
        long long child_end = child_base + types[child_tid].size;
        if (child_base <= addr && addr < child_end) {
            return find_addr_in_type(child_tid, child_base, addr, path + "." + member.name, answer);
        }
    }
    return false;
}

void query_address() {
    long long addr;
    cin >> addr;

    string answer;
    for (int i = 0; i < (int)elements.size(); i++) {
        int tid = type_id[elements[i].type_name];
        long long l = elements[i].start;
        long long r = l + types[tid].size;
        if (l <= addr && addr < r) {
            if (find_addr_in_type(tid, l, addr, elements[i].name, answer)) {
                cout << answer << '\n';
            } else {
                cout << "ERR\n";
            }
            return;
        }
    }

    cout << "ERR\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    add_basic_type("byte", 1);
    add_basic_type("short", 2);
    add_basic_type("int", 4);
    add_basic_type("long", 8);

    int q;
    cin >> q;
    while (q--) {
        int op;
        cin >> op;
        if (op == 1) {
            define_struct_type();
        } else if (op == 2) {
            define_element();
        } else if (op == 3) {
            query_path();
        } else {
            query_address();
        }
    }

    return 0;
}

复杂度

设一次结构体最多有 k 个成员,路径深度为 d

定义结构体需要 O(k)O(k);路径访问需要 O(d)O(d);地址反查最坏会扫描递归路径上各层成员,总复杂度不超过所有已定义成员数。

由于题目操作数最多 100,整体复杂度远小于限制。空间复杂度为 O(类型数+成员数+元素数)O(类型数 + 成员数 + 元素数)

总结

本题最容易错的是“结构体大小也要按最大对齐要求补齐”,以及地址反查时要区分真正的基本类型占用区间和对齐产生的空洞。

只要为每个类型保存 size/align/成员偏移,四种操作都可以直接从这些信息推出来。