试题内容请前往CCF官网查看:

CCF-CSP计算机软件能力认证考试
http://118.190.20.162/home.page

阅读本题解前,您应当了解下列知识:

  1. 线段树 教程
  2. C++ 标准库(含STL) 教程

这是一份以C++代码编写的CSP 专业组 202206题解。

请注意这是CSP-S/J的中学生竞赛的题解。

由于作者并非计算机专业科班出身,水平有限,并非每一题都能完整的解答,有完整解答者也不一定是最优解,现将模拟测试系统中的得分列举如下:

题目 得分 时间 内存
归一化处理 100 15ms 3.222MB
寻宝!大冒险! 100 15ms 3.171MB
角色授权 100 4.515s 70.87MB
光线追踪 100 1.156s 26.80MB
PS无限版 30 TLE 28.10MB

1 归一化处理

基础题。

遵循题面上给出的提示即可。

时间复杂度 O ( n ) O(n) O(n)。

#include<bits/stdc++.h>
using namespace std;int a[10010];int main() {int n,s=0;double avg,D=0.0,d;ios::sync_with_stdio(false);cin>>n;for(int i=0;i<n;i++){cin>>a[i];s+=a[i];}avg=(double)s/(double)n;for(int i=0;i<n;i++){D+=(a[i]-avg)*(a[i]-avg);}D=D/(double)n;d=sqrt(D);for(int i=0;i<n;i++){cout<<(a[i]-avg)/d<<endl;}return 0;
}

2 寻宝!大冒险!

基础题。

由于藏宝图左下角必定是一颗树,因此将地图上每一颗树作为藏宝图左下角匹配。匹配时使用二重循环,循环体使用setfind方法判定该位置是否有树。由于set基于红黑树实现,find方法的时间复杂度为 O ( l o g n ) O(log n) O(logn)。考虑到使用set存储树的位置,运算符重载是必要的。

时间复杂度 O ( n S 2 l o g n ) O(nS^2logn) O(nS2logn)。

#include<bits/stdc++.h>
using namespace std;struct pos{int r,c;pos():r(0),c(0){}pos(int _r,int _c):r(_r),c(_c){}void read(){cin>>r>>c;}bool operator<(const pos &p)const {if(r!=p.r){return r<p.r;}else{return c<p.c;}}pos operator+(pos p){return pos(r+p.r,c+p.c);}
};class full_mat{public:int n;vector<vector<bool> > dat;void read(int m){n=m;dat.resize(m+1);for(int i=m;i>=0;i--){dat[i].resize(m+1);for(int j=0;j<=m;j++){int t;cin>>t;dat[i][j]=(t==1);}}}
}; class sparse_mat{public:int n,L;set<pos> trees;void read(int _n,int _L){n=_n;L=_L;trees.clear();for(int i=0;i<n;i++){pos t;t.read();trees.insert(t);}}bool in_mat(pos p){return (p.r>=0 && p.c>=0 && p.r<=L && p.c<=L);}bool match(full_mat &sub_mat,pos origin){if(!in_mat(origin+pos(sub_mat.n,sub_mat.n))){return false;}for(int i=0;i<=sub_mat.n;i++){for(int j=0;j<=sub_mat.n;j++){auto it=trees.find(origin+pos(i,j));if(it==trees.end() && sub_mat.dat[i][j]){return false;}if(it!=trees.end() && !sub_mat.dat[i][j]){return false;}}}return true;}int count_match(full_mat &sub_mat){int ans=0;for(auto p:trees){if(match(sub_mat,p)) {ans++;}}return ans;}
};int main() {int n,L,s;sparse_mat pri;full_mat sub;ios::sync_with_stdio(false);cin>>n>>L>>s;pri.read(n,L);sub.read(s);cout<<pri.count_match(sub)<<endl;return 0;
}

3 角色授权

基础题。

CSP专业组的T3通常都是这种“阅读理解”题,难度不算大,一定要耐心审题。本题适合练习面向对象的程序设计思想。有几个要点要注意:

  1. 不要暴力枚举,很容易就超时了;

  2. 角色的存储建议使用map,这样查找角色的时间复杂度减至log级别;

  3. 角色关联的存储则也可以使用map,但是不要用角色作为关键字,因为我们查询时使用用户名或用户组,这样查询的复杂度也是log级别;

  4. 仿照CSS,我们可以将用户名和用户组统一起来,在用户名前加#,在用户组前加.,譬如.usergroup#user,这样可以将用户名和用户组统一用一个字符串表示;

#include<bits/stdc++.h>
using namespace std;struct oper{string op_name,res_type,res_name;void read(){cin>>op_name>>res_type>>res_name;}
};class __role{public:set<string> ops;bool any_op;set<string> res_types;bool any_res_type;set<string>    res_names;bool any_res_name;void __read(){int nv,no,nn;ops.clear();cin>>nv;any_op=false;for(int i=0;i<nv;i++){string s;cin>>s;ops.insert(s);if(s=="*") any_op=true;}res_types.clear();cin>>no;any_res_type=false;for(int i=0;i<no;i++){string s;cin>>s;res_types.insert(s);if(s=="*") any_res_type=true;}res_names.clear();cin>>nn;any_res_name=false;for(int i=0;i<nn;i++){string s;cin>>s;res_names.insert(s);}any_res_type=(res_names.size()==0);}bool can_perform_op(oper op){if(!any_op && ops.find(op.op_name)==ops.end()) return false;if(!any_res_type && res_types.find(op.res_type)==res_types.end()) return false;if(!any_res_name && res_names.find(op.res_name)==res_names.end()) return false;return true;}
};typedef pair<string,__role> role;role create_role(){role ret;cin>>ret.first;ret.second.__read();return ret;
}typedef pair<string,set<string> > auth_item; //{role_name: ex-usernames}auth_item create_auth_item(){auth_item ret;int ns; ret.second.clear();cin>>ret.first>>ns;for(int i=0;i<ns;i++){string type,name;cin>>type>>name;if(type=="u") {ret.second.insert("#"+name);}else{ret.second.insert("."+name);} }return ret;
}class user{public:string name;set<string> groups;void read(){int ng;cin>>name>>ng;groups.clear();for(int i=0;i<ng;i++){string s;cin>>s;groups.insert(s);}}
};//角色关联反向存储表
class auth_rtable{private:void set_merge(set<string> &dst,set<string> &src){for(auto e:src){dst.insert(e);}}
public:map<string,set<string> > rmp; //{ex-username:roles}void add_auth_item(auth_item &itm){for(auto uname:itm.second){auto it=rmp.find(uname);if(it==rmp.end()){set<string> ns;ns.insert(itm.first);rmp.insert(make_pair(uname,ns));}else{it->second.insert(itm.first); }}}set<string> get_user_all_role_name(user &usr){set<string> ret;auto it=rmp.find("#"+usr.name);if(it!=rmp.end()) {set_merge(ret,it->second);}for(auto grp:usr.groups){auto it=rmp.find("."+usr.name);if(it!=rmp.end()) {set_merge(ret,it->second);}}}
};class database{public:map<string,__role> roles;auth_rtable auth;void read(int n,int m){roles.clear();for(int i=0;i<n;i++){roles.insert(create_role());}for(int i=0;i<m;i++){auth_item ai=create_auth_item();auth.add_auth_item(ai);}}bool authenticate(user &usr,oper &op){auto all_role_name=auth.get_user_all_role_name(usr);for(auto r:all_role_name){if(roles[r].can_perform_op(op)) return true;}return false;}
};int main(){ios::sync_with_stdio(false);database db;int n,m,q;cin>>n>>m>>q; db.read(n,m);while(q--){user usr;oper op;usr.read();op.read();cout<<(db.authenticate(usr,op)?1:0)<<endl;}return 0;
}

注:该程序在查询时(db.authenticate)将用户对应的所有角色都列出来了,这是没有必要的,可以再优化。

4 光线追踪

中等题。

容易看出:

1.光线总是在平行于坐标轴的方向上传播;

2.反射点的坐标总是整数;

3.反射次数至多为 l o g 0.8 1 0 − 9 ≈ 93 log_{0.8}10^{-9}\approx 93 log0.8​10−9≈93次,记反射次数为 h h h;

本题最精巧之处即是“所有反射面的 ∣ x 1 − x 2 ∣ |x_1-x_2| ∣x1​−x2​∣之和不超过 3 × 1 0 5 3\times 10^5 3×105”这个条件,这意味着,反射点的数量不会超过个 3 × 1 0 5 3\times 10^5 3×105, 此即解题关键。

假设我们现有点 P ( x 0 , y 0 ) P(x_0,y_0) P(x0​,y0​),和方向 d = 0 d=0 d=0( x x x增加方向),那么我们需要找出直线 y = y 0 y=y_0 y=y0​上横坐标比 x 0 x_0 x0​大的第一个点。

为了解决上述问题,我们可以设计一个字典:其为点的纵坐标 y 0 y_0 y0​,键值为一个排好序的数组,存储 y = y 0 y=y_0 y=y0​上所有反射点的横坐标,例如:

Key Value 实际的点
2 -1,3 (-1,2)(3,2)
3 1,2,4 (1,3)(2,3)(4,3)

当方向 d d d是 y y y轴的方向时,我们还需要一个上述的字典:其键为点的横坐标 x 0 x_0 x0​,键值为一个排好序的数组,存储 x = x 0 x=x_0 x=x0​上所有反射点的纵坐标。

在C++中使用map<int, set<int> >可以方便查找 y = y 0 y=y_0 y=y0​键的键值,并将键值中的横坐标都排好序,从而实现上述功能。mapset动态修改(inserterase)和查询(lower_boundupper_bound,即二分查找)的时间复杂度均是log级别的,速度较快。这意味着,每次进行3操作的时间复杂度不高于 O ( h log ⁡ 2 m ) O(h\log^2m) O(hlog2m);1操作则相当于将镜面上的整点加入字典,其时间复杂度不高于 O ( ∣ x 2 − x 1 ∣ log ⁡ 2 m ) O(|x_2-x_1|\log^2m) O(∣x2​−x1​∣log2m)。

再考虑2操作。相当于把对应点从字典中删除,即1的逆操作。其时间复杂度也不高于 O ( ∣ x 2 − x 1 ∣ log ⁡ 2 m ) O(|x_2-x_1|\log^2m) O(∣x2​−x1​∣log2m)。

总的时间复杂度不会超过 O ( ( h + Σ x ) log ⁡ 2 m ) O((h+\Sigma x) \log^2m) O((h+Σx)log2m)。

事实上,下面的代码使用map<int, map<int,int> >存储所有的反射点。内层的map<int,int>的键为某点横/纵坐标,键值为该点对应镜面的编号,这便于反向查找某个反射点对应的镜面,从而获得该镜面的反射率和倾角,便于计算反射后光的方向与强度。

#include<iostream>
#include<map>
#include<cmath>
using namespace std;enum class LightDir {X_POS = 0,Y_POS = 1,X_NEG = 2,Y_NEG = 3
};
int dx[] = { 1,0,-1,0 }, dy[] = {0,1,0,-1};int sgn(int x) {if (x > 0) return 1;if (x < 0) return -1;return 0;
}struct point {int x, y;bool operator==(const point& p) const {return x == p.x && y == p.y;}
};typedef pair<point, int> point_with_int;
const point NULL_POINT = { 2e9, 2e9 };
const point_with_int NULL_PD = { NULL_POINT, -1 };struct mirror {int x1, y1, x2, y2, k, id;double a;void read(int id) {cin >> x1 >> y1 >> x2 >> y2 >> a;if (x1 > x2) {swap(x1, x2); swap(y1, y2);}k = (y2 - y1) / (x2 - x1);this->id = id;}
}mirrors[200001];class space {/** psx = 所有镜面整点按x方向排序;* key存放所有点的y值,value递增存储对应x坐标和倾斜方向* psy = 所有镜面整点按y方向排序;* key存放所有点的x值,value递增存储对应y坐标和倾斜方向*/map<int, map<int,int> > psx, psy;void add_point(int x, int y, int id) {auto it = psx.find(y);if (it == psx.end()) {map<int, int> add;add.insert({ x,id }); psx.insert({ y, add });}else {it->second.insert({ x,id });}it = psy.find(x);if (it == psy.end()) {map<int, int> add;add.insert({ y,id }); psy.insert({ x, add });}else {it->second.insert({ y,id });}}   void del_point(int x, int y) {psx[y].erase(x);psy[x].erase(y);}public:void add_mirror(mirror m) {for (int x = m.x1 + 1, y = m.y1 + m.k; x < m.x2; x++, y += m.k) {add_point(x, y, m.id);}}void del_mirror(mirror m) {for (int x = m.x1 + 1, y = m.y1 + m.k; x < m.x2; x++, y += m.k) {del_point(x, y);}}//返回反射点和反射面编号point_with_int find_nearst_reflect_point(point p, LightDir d) {if (d == LightDir::X_POS || d == LightDir::X_NEG) {auto it = psx.find(p.y);if (it == psx.end()) return NULL_PD;map<int, int>::iterator it2;if (d == LightDir::X_POS) {it2 = it->second.upper_bound(p.x);if (it2 == it->second.end()) return NULL_PD;}else {it2 = it->second.lower_bound(p.x);if (it2 == it->second.begin()) return NULL_PD;--it2;//技巧:lower_bound的前一个就是第一个比p.x小的数}return { {it2->first,p.y}, it2->second };}else {auto it = psy.find(p.x);if (it == psy.end()) return NULL_PD;map<int, int>::iterator it2;if (d == LightDir::Y_POS) {it2 = it->second.upper_bound(p.y);if (it2 == it->second.end()) return NULL_PD;}else {it2 = it->second.lower_bound(p.y);if (it2 == it->second.begin()) return NULL_PD;--it2;}return { {p.x,it2->first}, it2->second };}}
}instance;LightDir next_dir(LightDir dir, int mirror_k) {if (dir == LightDir::X_POS) {return mirror_k == 1 ? LightDir::Y_POS : LightDir::Y_NEG;}else if (dir == LightDir::X_NEG) {return mirror_k == -1 ? LightDir::Y_POS : LightDir::Y_NEG;}else if (dir == LightDir::Y_POS) {return mirror_k == 1 ? LightDir::X_POS : LightDir::X_NEG;}else {//(dir == LightDir::Y_NEG) return mirror_k == -1 ? LightDir::X_POS : LightDir::X_NEG;}
}
pair<point, int> test_source(point p, LightDir d, double I, int T) {do {point_with_int ret = instance.find_nearst_reflect_point(p, d);point p2 = ret.first; int id = ret.second;int dist = abs(p.x - p2.x) + abs(p.y - p2.y);if (p2 == NULL_POINT || dist > T) {p.x += dx[(int)d] * T;p.y += dy[(int)d] * T;break;}p = p2;d = next_dir(d, mirrors[id].k);I = I * mirrors[id].a;if (I < 1.0) {return { {0,0},0 };}T -= dist;} while (T > 0);return { p,(int)I };
}int main() {ios::sync_with_stdio(false);int m;cin >> m;for (int i = 1; i <= m; i++) {int op; cin >> op;if (op == 1) {mirrors[i].read(i);instance.add_mirror(mirrors[i]);}else if (op == 2) {int k; cin >> k;instance.del_mirror(mirrors[k]);}else {int x, y, d, t;double I;cin >> x >> y >> d >> I >> t;auto ans = test_source({ x,y }, (LightDir)d, I, t);cout << ans.first.x << ' ' << ans.first.y << ' ' << ans.second << endl;}}return 0;
}

5 PS无限版(仅部分思路)

中等题。

对于前30%的分数,我们每次操作直接遍历 [ l , r ] [l,r] [l,r]即可。

对于后70%的分数,我们可以构建一个线段树,维护每一段的平方之和。维护是容易的,而维护平方之和的方法我还没有想到,望有大佬多多指教。

附录:几种变换的推导

1. 投影

记投影直线为 y = k x + b y=kx+b y=kx+b,待投影点为 ( x 0 , y 0 ) (x_0,y_0) (x0​,y0​),则投影点 ( x 1 , y 1 ) (x_1,y_1) (x1​,y1​)满足 { y = k x + b k ( y − y 0 ) = − ( x − x 0 ) \left\{\begin{array}{c} y=kx+b\\ k(y-y_0)=-(x-x_0) \end{array}\right. {y=kx+bk(y−y0​)=−(x−x0​)​
解得
{ x 1 = x 0 + k ( y 0 − b ) k 2 + 1 y 1 = k x 1 + b \left\{\begin{array}{c} x_1=\frac{x_0+k(y_0-b)}{k^2+1}\\ y_1=kx_1+b \end{array}\right. {x1​=k2+1x0​+k(y0​−b)​y1​=kx1​+b​

2. 对称

有直线为 l : y = k x + b l:y=kx+b l:y=kx+b,线外一点 P ( x 0 , y 0 ) P(x_0,y_0) P(x0​,y0​), P P P在 l l l上的投影点为 M ( x 1 , y 1 ) M(x_1,y_1) M(x1​,y1​),则 P P P关于 l l l的对称点为 Q ( 2 x 1 − x 0 , 2 y 1 − y 0 ) Q(2x_1-x_0,2y_1-y_0) Q(2x1​−x0​,2y1​−y0​)。

CSP 202206 题解:归一化处理,寻宝大冒险,角色授权,光线追踪,PS无限版相关推荐

  1. CCF CSP——202206-5 PS无限版(70分题解)

    问题描述 试题链接:PS无限版 70分题解 题目还是比较简单的,不至于长篇大论的(读题都累死人了),此题要求:有一系列的点(用数组保存下来),编号(1,2--,n),对编号[l,r]的点进行不同的7个 ...

  2. ccfcsp 202206-2 寻宝大冒险

    问题描述 就是这个题,输入与输出格式不再罗列,题目目的大概意思为:给定一个小的地图B,在一个大的地图A中找到B,也就是B是A的子图,求有几个这样的子图. ccf的第二题给定的限制时间和空间都很大,所以 ...

  3. 2022-6-2寻宝大冒险(不用前缀和与map,c/c++实测满分)

    总结: 离散点对应回稀疏矩阵时可以分块处理,根据限制条件筛选掉不必处理的块,缩短计算时间. 一.题目要求 题目背景 暑假要到了.可惜由于种种原因,小 P 原本的出游计划取消.失望的小 P 只能留在西西 ...

  4. ccf寻宝!大冒险!python满分(敲开心~)

    寻宝 大冒险 题目传送:http://118.190.20.162/view.page?gpid=T147 思路及代码: 核心点就是哈希. 之前刷的是70分,找不到之前的代码了,大概是建了一个很大的表 ...

  5. CSP 寻宝!大冒险! C++

    CSP 寻宝!大冒险! C++ 思路:遍历绿化图的每个树,先判断藏宝图里树的数量和当前树的右上边长为s的正方形的树的数量是否一致,如果一致,再循环遍历判断是否相等. #include<bits/ ...

  6. ccf csp寻宝!大冒险!(C语言)

    ccf csp寻宝!大冒险! 题目背景 暑假要到了.可惜由于种种原因,小 P 原本的出游计划取消.失望的小 P 只能留在西西艾弗岛上度过一个略显单调的假期--直到-- 某天,小 P 获得了一张神秘的藏 ...

  7. CCF- CSP 202206-2寻宝!大冒险!暴力算法满分题解

    CCF- CSP 202206-2寻宝!大冒险!暴力算法满分题解 题目链接:202206-2寻宝!大冒险! 思路: 数据范围中n ≤ \leq ≤ 1000,S ≤ \leq ≤ 50,考虑时间复杂度 ...

  8. 4510. 寻宝!大冒险!

    Powered by:NEFU AB-IN Link 文章目录 4510. 寻宝!大冒险! 题意 思路 代码 4510. 寻宝!大冒险! 题意 第26次CCF计算机软件能力认证 题目略 简短版:有一个 ...

  9. ~1 ccf 2022-06-2 寻宝!大冒险!

    寻宝!大冒险! 题目描述 输入 输出 样例输入 样例输出 子任务 源代码 关于这题 题目描述 输入 输出 样例输入 样例一: 5 100 2 0 0 1 1 2 2 3 3 4 4 0 0 1 0 1 ...

最新文章

  1. C++实现大数的加法
  2. php中public放什么,PHP中常用关键字public, private, protected, static...
  3. 计算机网络离不开光缆,九年级物理全册 第二十一章 第四节 越来越宽的信息之路习题课件 新人教版.ppt...
  4. 实地审核和系统审核_在线监测系统运行情况审核
  5. 群集lvs—DR的配置及应用
  6. easyui-window 关闭事件,只要关闭窗口就会触发
  7. 【LCT】魔法森林(P2387)
  8. 为什么虚拟机上一运行就显示程序停止_五分钟学Java:如何学习Java面试必考的JVM虚拟机...
  9. (04)FPGA芯片选型原则
  10. git.exe 启动 慢_户外慢生活节来了!南京固城湖水慢城开启春日度假模式
  11. 启动hadoop遇到的datanode启动不了
  12. 拉取ftp服务器上的文件_无公网IP环境下搭建外部访问FTP文件共享服务器
  13. 1、视觉slam简介
  14. google 翻译 api
  15. Java解析KML文件
  16. Mac Pycharm导入Pygame教程(超细)
  17. 今天不学习,明天变垃圾
  18. rstudio的数据集怎么建立和保存_用RStudio导入数据
  19. 威联通 php升级,威联通折腾篇十七:Docker 安装的 NextCloud 升级、备份及恢复
  20. 并不对劲的bzoj1095:p2056:[ZJOI2007]捉迷藏

热门文章

  1. 环签名、群签名、DAA协议原理
  2. Linux ping6 本地ipv6地址(local ipv6 address) 无效的参数(Invalid argument)
  3. 2007年通过做调查你一样能获得比较稳定的收入
  4. python实现svm源程序_python实现SVM
  5. DOS命令:attrib
  6. 零跑汽车腾空出世,倍受车迷喜爱
  7. Microsoft VBScript 编译器错误 错误 '800a0401' 语句未结束 的解决方法
  8. python数组中逗号+冒号的使用
  9. 三星手机无法读取相册图片
  10. 微信插件数据获取失败的解决方案