又到了刷真题的时间。这道 CSP-S 2023 的题,考点是模拟。
题目来源:ACGO A7094 | 洛谷 P9754
难度定位:提高+/省选- | 通过率:56.85%
一、题目信息
题目名称:结构体
所属赛事:2023 年 CSP-S 组复赛
官方难度:提高+/省选-
涉及知识点:模拟
题目链接:https://www.acgo.cn/problemset/info/7094
洛谷编号:P9754(同一道题,两个平台均可提交,代码通用)
坑点(无数人 WA 的核心)
- 按题意逐步实现时,把每个操作拆成独立步骤,别把多个条件揉进一个式子
- 规则里的『首次满足』『优先最早』这类措辞要落实成具体的比较顺序
- 高难度题的暴力分很关键,先写一版能过的部分分再想正解
题意简述
先把题目翻译成人话:
在 C++ 等高级语言中,除了 int 和 float 等基本类型外,通常还可以自定义结构体类型。在本题当中,你需要模拟一种类似 C++ 的高级语言的结构体定义方式,并计算出相应的内存占用等信息。
题目背景
在 C++ 等高级语言中,除了 int 和 float 等基本类型外,通常还可以自定义结构体类型。在本题当中,你需要模拟一种类似 C++ 的高级语言的结构体定义方式,并计算出相应的内存占用等信息。
题目描述
在这种语言中,基本类型共有 4 种:byte、short、int、long,分别占据 1、2、4、8 字节的空间。
定义一个结构体类型时,需要给出类型名和成员,其中每个成员需要按顺序给出类型和名称。类型可以为基本类型,也可以为先前定义过的结构体类型。注意,定义结构体类型时不会定义具体元素,即不占用内存。
定义一个元素时,需要给出元素的类型和名称。元素将按照以下规则占据内存:
- 元素内的所有成员将按照定义时给出的顺序在内存中排布,对于类型为结构体的成员同理。
- 为了保证内存访问的效率,元素的地址占用需要满足对齐规则,即任何类型的大小和该类型元素在内存中的起始地址均应对齐到该类型对齐要求的整数倍。具体而言:
- 对于基本类型:对齐要求等于其占据空间大小,如
int类型需要对齐到 4 字节,其余同理。 - 对于结构体类型:对齐要求等于其成员的对齐要求的最大值,如一个含有
int和short的结构体类型需要对齐到 4 字节。
以下是一个例子(以 C++ 语言的格式书写):
struct d { short a; int b; short c; }; d e;该代码定义了结构体类型d与元素e。
前置知识
在动笔之前,把这题用到的知识铺垫一下,不熟悉的跟着看。
按题意一步步走
模拟题不考算法,考的是「把话翻译成代码」的耐心。读题时把变量、循环、边界一条条列出来,再动手写,比边写边想快得多。
二、思路分析
CSP-S 2023 中难度为提高+/省选-,中场,先拿暴力分是合理的选择。
来推一下这题该怎么做。
题目解析
题目给了四种基本类型,通过基本类型定义结构体,通过类型定义变量。你要模拟出变量占用的位置。
解释一下:对于每一个类型,有 3 个基本参数:名称、对齐要求、大小。
对于结构体类型,还有非基本的参数,也就是他们里头的元素的名称、对齐要求、大小等。
对于每一个元素,有 3 个参数:名称、类型、首地址。
对于基本类型,需要在一开始就给他参数初始化。
基本是大模拟,注意细节即可。
三、完整代码
// CSP-S 2023 结构体 // 模拟一种类 C++ 语言的结构体定义与内存布局(对齐规则): // 基本类型:byte(1)/short(2)/int(4)/long(8),各自的「对齐字节数」= 自身大小。 // 结构体的对齐字节数 = 其成员对齐数的最大值;结构体大小 = 成员首尾相接后, // 按自身对齐数向上取整。 // 四类操作(n 条,行首为操作类型): // 1 name k f1 t1 ... fk tk :定义名为 name 的结构体类型,含 k 个成员 // 2 type name :定义 type 类型的变量 name(全局) // 3 a.b.c :查询成员 a.b.c 的绝对地址偏移 // 4 addr :查询绝对地址 addr 落在哪个成员上,输出成员路径或 ERR // 直接模拟即可,O(n·成员数)。 #include <cstdio> #include <iostream> #include <string> #include <algorithm> using namespace std; struct kd{ int k,bz,tp[101]; long long sz,lst[101][2]; string nms[101]; }; struct ad{ int l; string S[100]; }; int n,op,l=5,flag,k; long long addr; kd kds[105]; string names[105],tp,nm; ad AD; int fd(string name){ for(int i=0;i<l;i++){ if(names[i]==name){ return i; } } return -1; } long long f(long long a,int b){ return(a+b-1)/b*b; } void add(int id_1,string nm,int id_2){ kds[id_1].k++; kds[id_1].tp[kds[id_1].k]=id_2; kds[id_1].nms[kds[id_1].k]=nm; kds[id_1].lst[kds[id_1].k][0]=f(kds[id_1].lst[kds[id_1].k-1][1],kds[id_2].bz); kds[id_1].lst[kds[id_1].k][1]=kds[id_1].lst[kds[id_1].k][0]+kds[id_2].sz; kds[id_1].bz=max(kds[id_1].bz,kds[id_2].bz); kds[id_1].sz=f(kds[id_1].lst[kds[id_1].k][1],kds[id_1].bz); return; } void init_basic(int ind,string name,long long sz,int bz){ kds[ind].bz=bz; kds[ind].sz=sz; names[ind]=name; return; } void init(){ init_basic(1,"byte",1,1); init_basic(2,"short",2,2); init_basic(3,"int",4,4); init_basic(4,"long",8,8); return; } ad ipt(){ char c=getchar(); ad ans;ans.l=0; while(c<'a'||c>'z'){ c=getchar(); } while((c>='a'&&c<='z')||c=='.'){ if(c=='.'){ ans.l++; }else{ ans.S[ans.l]+=c; } c=getchar(); } ans.l++; return ans; } void opt(ad a){ for(int i=0;i<a.l-1;i++){ cout<<a.S[i]<<"."; } cout<<a.S[a.l-1]<<endl; return; } long long solve1(int now,int ind){ if(ind==AD.l){ return 0; } for(int i=1;i<=kds[now].k;i++){ if(kds[now].nms[i]==AD.S[ind]){ return kds[now].lst[i][0]+solve1(kds[now].tp[i],ind+1); } } return-1; } void solve2(int now,long long addr){ if(addr>=kds[now].sz){ flag=1; return; } if(kds[now].k==0){ return; } for(int i=1;i<=kds[now].k;i++){ if(kds[now].lst[i][0]<=addr&&kds[now].lst[i][1]>addr){ AD.S[AD.l++]=kds[now].nms[i]; solve2(kds[now].tp[i],addr-kds[now].lst[i][0]); return; } } flag=1; return; } int main(){ scanf("%d",&n); init(); for(int i=0;i<n;i++){ scanf("%d",&op); if(op==1){ cin>>names[l]; scanf("%d",&k); for(int j=0;j<k;j++){ cin>>tp>>nm; add(l,nm,fd(tp)); } printf("%lld %d\n",kds[l].sz,kds[l].bz);l++; }else if(op==2){ cin>>tp>>nm; add(0,nm,fd(tp)); printf("%lld\n",kds[0].lst[kds[0].k][0]); }else if(op==3){ AD=ipt(); printf("%lld\n",solve1(0,0)); }else{ scanf("%lld",&addr); AD.l=0; flag=0; solve2(0,addr); if(flag){ printf("ERR\n"); }else{ opt(AD); } } } return 0; }四、代码分段拆解
把代码拆成几块来看,逐块理解比整体啃更容易。
头文件与全局变量
先把用到的头文件和全局变量放上来。全局变量在竞赛里很常用,好处是函数之间共享方便,代价是要小心初始化和被意外改写。
#include<cstdio> #include<iostream> #include<string> #include<algorithm>using namespace std; struct kd{ int k,bz,tp[101]; long long sz,lst[101][2]; string nms[101]; struct ad{ int l; string S[100]; int n,op,l=5,flag,k; long long addr; string names[105],tp,nm; int fd(string name){ long long f(long long a,int b){ char c=getchar();主体逻辑
主体部分按顺序完成读入、计算、输出。main函数尽量保持线性,把复杂的逻辑拆成独立函数,出问题时更容易定位。
边界处理
这类题最容易在边界上翻车:最小值、最大值、只剩一个元素的情况,都在脑中过一遍。
五、实例模拟
拿真实样例跑一遍,看看逻辑对不对得上。
样例 1
输入:
5 1 a 2 short aa int ab 1 b 2 a ba long bb 2 b x 3 x.ba.ab 4 10输出:
8 4 16 8 0 4 x.bb以第一组样例为例,输入是5、1 a 2、short aa、int ab、1 b 2、a ba、long bb、2 b x、3 x.ba.ab、4 10,正确输出是8 4、16 8、0、4、x.bb。建议把这两行数据抄在纸上,用上面的代码手动走一遍,重点看循环变量怎么变化、答案在哪一步被更新。能自己推出来的东西,比看懂代码印象深得多。
六、踩坑总结
复盘一下,这些点记住下次能省很多调试时间。
- 按题意逐步实现时,把每个操作拆成独立步骤,别把多个条件揉进一个式子
- 规则里的『首次满足』『优先最早』这类措辞要落实成具体的比较顺序
- 高难度题的暴力分很关键,先写一版能过的部分分再想正解
另外提醒一句:这题在 CSP-S 2023 里是提高+/省选-难度,考场上如果没有把握拿满分,先把暴力写法交上去拿到部分分,再回头优化。CSP 是打分制,空着不写才是最亏的。
七、知识点拓展
这题的核心是模拟。
模拟题在 CSP 里属于「必须拿满」的部分。它的训练价值不在于算法,而在于把一段自然语言描述准确翻译成代码的能力。训练方法很简单:读完题先不写代码,用中文把步骤一步步列出来,再逐条翻译。
建议的练习路径:
- 先把本题独立写一遍,不看代码,遇到卡壳再看提示
- 换个平台提交(ACGO 和洛谷都试试),确保代码在两个平台都能过
- 找两到三道同类型的题练手,巩固这类问题的识别能力
八、适用人群
如果你是下面这几种情况,这题值得动手写一遍:
- 目标是 CSP-S 高分或省选的同学
- 想通过真题理解高难度题如何从暴力优化到正解的同学
- 想弄明白模拟类问题标准解法的同学
如果这篇对你有帮助,点个赞让更多备考的同学看到。
关于这个系列
这是「CSP 真题逐题精讲」的第 39 篇。我会按年份从 2019 一路刷到 2025,
CSP-J 和 CSP-S 全覆盖。每篇都给:双平台题号(ACGO + 洛谷)+ 逐行代码拆解 + 真实样例模拟。
跟着刷完这一套,CSP 第一轮的选择题和基础题基本就稳了。
下一篇:CSP-S 2023 种树(提高+/省选-)
👉 关注我,不错过后续更新。评论区告诉我你现在刷到哪一年了,我看看能不能按大家的进度调整更新节奏。