绍大2022级ACM集训队新生选拔赛题解(更新中)
绍大2022级ACM集训队新生选拔赛题解(更新中)
A.Honest
大致题意
在一个 n*n 的矩阵统计 “honest” 这个单词的个数。
基本思路
本题是签到题,只要用二维数组读入字符矩阵遍历统计即可。
代码
#include<bits/stdc++.h>
using namespace std;
int main() {
int n;
bool fg=0;
while(cin>>n) {
if (n==0) break;
if (fg) cout<<endl;
fg=1;//输出换行
char a[30][30];//字符数组
for (int i=1; i<=n; i++) {
for (int j=1; j<=n; j++) cin>>a[i][j];
}
int ans=0;
for (int i=1; i<=n; i++) {
for (int j=1; j<=n; j++) {
string s="";
string s1="";
for (int k=0; k<6; k++) {
if (j+k<=n) s=s+a[i][j+k]; //横向统计,超出矩阵范围不统计。
if (i+k<=n) s1=s1+a[i+k][j]; //纵向统计。
}
if (s=="honest") ans++;
if (s1=="honest") ans++;
}
}
cout<<ans;
}
return 0;
}
建议
尽早解决,避免罚时过高。
B.最短购物距离
大致题意
给定 n 个商店的坐标,选择一个商店作为起点,每次到达一个商店并放回,要求走完所有商店,求最少的总路程。
基本思路
由于 n 的数据范围非常小(只有),所以只需要枚举每个商店作为起点,计算总路程并统计最短路程输出即可。
代码
#include<bits/stdc++.h>
using namespace std;
int main() {
int T;
cin>>T;
while(T--) {
int a[200]={0};
int n;
cin>>n;
for (int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+1+n);
int ans=0x3f3f3f3f; // 开始假设最短路程为无穷大(超过题目最大路程的数即可视为无穷大)
for (int i=1;i<=n;i++){
int now=0;
for (int j=1;j<=n;j++){//枚举商店
now=now+abs(a[j]-a[i])*2;//计算路程之和,到达后目的地后要回到起点,所以要乘2
}
ans=min(ans,now);//取最短路程
}
cout<<ans<<endl;
}
return 0;
}
建议
题目数据范围过小时,可以选择用暴力枚举解决问题,以减少思考时间,降低罚时。
C.多少签多少钱
大致题意
有 n 种签子,给定每种签子的数量,价格和折扣,计算签子总数和总价格。
基本思路
直接求和即可,由于折扣会导致价格出现小数,所以要用 类型的变量表示价格。
代码
#include<bits/stdc++.h>
using namespace std;
int main() {
int T;
bool fg=0;
cin>>T;
int cnt=0;
while(T--) {
if (fg) cout<<endl;
fg=1;
cnt++;//记录case
int n;
cin>>n;
int ansn=0;//记录签子总数
double ans=0;//记录总价格
for (int i=1;i<=n;i++){
int x;
double y,z;
cin>>x>>y>>z;
ansn+=x;
ans=ans+(x*y*(z/10));
}
printf("Case #%d:%d %.1lf",cnt,ansn,ans);//价格保留1位小数
}
return 0;
}
建议
写程序前,应提前根据题目描述想好要用哪些类型的变量。
D.小学数学
大致题意
给定一个 n ,判断 n 的立方能否等于 n 个连续奇数之和,找到并输出这些连续奇数。
基本思路
这道题是一道找规律的题,从样例中不难看出, 1 的立方由 1 个奇数组成,2 的立方由俩个奇数组成,3 的立方由三个奇数组成,所以不难推出 n 的立方就是第 (1+2+...+n-1+1)个奇数后面连续的 n 个奇数之和。
当然,题目中还有 "-1" (即不存在)的情况1,事实上,当 n 为正数时都满足以上情况,只有 n<=0 时不存在。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
int T;
bool fg=0;
cin>>T;
while(T--) {
if (fg) cout<<endl;
fg=1;
ll n;
cin>>n;
if (n<=0) {
cout<<"-1";
continue;
}//n<=0时不存在要求情况
ll now=(n-1+1)*(n-1)/2;
now++;
now=now*2-1; //计算连续奇数的第一个数
for (int i=1;i<=n;i++){
if (i!=1) cout<<" "; //末尾无空格
cout<<now;
now=now+2;
}
}
return 0;
}
建议
遇到和数学有关的题,可以尝试一下能否从样例或者自己拟造的例子中找出规律。(事实上,很多类型的题都能够找规律,这种题不涉及算法,只考察思维)
E.游戏
大致题意
俩个人玩游戏,有一堆总数为 n 的柚子,双方轮流进行操作(俩人都采用最优策略),每次操作可以拿走 一个质数的幂次个数 的柚子(即 ), 最后拿走柚子的人获胜。给定一个 n ,询问先手操作的Wei能否获胜。
基本思路
这是一道博弈类型的题目,属于博弈题中最简单的一类。
对于此类追求胜负的博弈题,我们首先明确要一个概念,这个概念名为 必胜态 。
从字面上理解,必胜态就是必胜的状态,例如题中 n=1 或 n=2 时,Wei 都能直接把柚子取完,即 Wei 必胜,则我们称 n=1和 n=2 的状态为必胜态。
这样,其他不是必胜态的状态,就变成了必输的局面,称之为 必败态。
以上的所有概念与结论,都有一个很重要的前提,即 俩人都采用最优策略 ,因为俩人都想方设法地赢,所以不会有哪一次操作出现失误,这样就不会出现 可能输或可能赢 的情况,即所有状态不是必胜就是必败。
在了解了博弈的基础知识后,回来看这道题,我们能发现:当 n=1-5 时,都为必胜态,6 为第一个必败态。
我们先把所有的质数和他们的幂筛选出来,因为这些数明显是必胜态。
筛选完之后,20 以内的数还剩下 : 6 10 12 14 15 18
由于 6 已经确定为必败态,所以当哪一个人开始操作时如果还剩 6 个柚子,他一定会输。
所以当 n=10 时,如果 Wei 拿走 4 个柚子,使柚子总数变成 6 ,那么就是 Peng 陷入了必败态,Wei 必胜,所以 10 也是必胜态。
当 n=12 时,Wei 只能拿走 2 个柚子,不然剩下的柚子都会被 Peng 一次性拿完,这样使 Peng 开始操作时还剩下 10 个柚子,此时 Peng 进入了必胜态,也就是 Wei 陷入了必败态,所以 12 也是 必败态。
接下来,由于 14,15 这俩个数Wei 都能使他们变成 12 ,即让 Peng 陷入必败态,所以 14 15 也是必胜态,而对于 n=18 的情况,不管 Wei 如何取, Peng 都能让他 变为 12 或者 6 ,即让 Wei 陷入必败态,所以 18 也是必败态。
至此,我们已经能猜到规律:只要 n 为 6 的倍数,Wei 就一定陷入必败态,反之皆为必胜态。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main(){
ll n;
bool fg=0;
while(cin>>n){
if (n%6==0) {
cout<<"No"<<endl;
}else cout<<"Yes"<<endl;
}
return 0;
}
建议
在学习完c++基础语法后,可以尝试学习一下各种算法,扩充自己的算法知识库,这样更有利于面对比赛时的各种情况。
F.包子
大致题意
给定一个范围 w 和一个整数 n 以及 n 个 整数,要求找出一共有多少个整数 k (),使得 k 在加上这 n 个整数时始终保持()。
基本思路
由于本题的 w 的数据范围为 (),且有多组数据,所以不能采用暴力枚举的方法 (从 1 枚举到 w) 来求 k 的个数。
我们首先对每个整数 a[i] 求他的前缀和 (即) ,然后找出前缀和中的最大值和最小值。
如果前缀和的最大值或最小值的绝对值超过了 w ,则当 k 加到 最大值或最小值位置 时,一定会出现 k <0 或 k>w 的情况,则该 k 不满足要求。
之后的情况,我们用mx表示前缀和最大值,用mn表示最小值,分三种情况讨论:
1 : 当 时,若用 表示 k 的答案区间,则 可以取到 0 ,而 不能超过 ,所以一共有 个 k 可取。
2:当 时, 可以取到 w ,但 不能小于 (||) 所以 , 一共有 个 k 可取。
3:当 但 时, 不能小于 (||) 而 不能超过 ,所以 , ,
一共有 个 k 可取。
至此,讨论结束。
代码
#include<bits/stdc++.h>
#include<map>
using namespace std;
typedef long long ll;
const int N=1005;
const ll INF=1e9+7;
int main() {
bool fg=0;
int n;
ll w;
while(cin>>n>>w) {
if (fg) cout<<endl;
fg=1;
ll a[1005]={0};
ll mx=-INF,mn=INF;//最大值和最小值
ll pre=0;
for (int i=1;i<=n;i++){
ll x;
cin>>x;
pre=pre+x;
mx=max(pre,mx);
mn=min(mn,pre);
}
if (abs(mn)>w || abs(mx)>w) {
cout<<"0";
}else {
ll l=0,r=w;
if (mn<0) {
if (mx<0) {
l=abs(mn);
}else {
l=abs(mn);
r=w-abs(mx);
}
}else {
r=w-mx;
}
cout<<r-l+1;
}
}
return 0;
}
建议
根据题目的数据范围,选择合适的做法。
G.数一数素数
大致题意
给定一个区间 ,输出该区间内的素数个数。
基本思路
由于该题数据范围只有五百万,我们完全可以将五百万以内的素数全部筛选出来,所以这题主要考虑的是筛选素数的方法。
筛选素数的方法一般有俩种,一种是埃式筛法,另一种是线性筛,俩种筛法的区别在于筛选的方式不同,从而导致效率也不同(线性筛会快很多),想要了解线性筛,可以点这里。
但是不管用哪一种筛法,我们都只需要筛选一次,即在回答询问前,先将五百万以内的素数筛选并记录当前位置素数个数,很多同学在比赛时超时的原因是对于每一次询问,都进行了一次筛选,这就造成了时间上的冗余,增加了时间复杂度。
筛出素数后,我们只要记录到当前位置有多少个素数即可,想要知道区间内有多少个素数,只要用俩个前缀和相减的方法来求区间和即可。
由于大家已经学习过埃式筛法,这里给出的代码采用的线性筛的方法。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5000005;
bool np[N];
int prime[N];
int ct[N];//ct[i]记录到i为止一共有多少个素数
int cnt=0;
void init(int n){
np[0]=1;
np[1]=1;
for (int i=2;i<=n;i++){
if (!np[i]){
prime[++cnt]=i;
ct[i]=ct[i-1]+1;
}else ct[i]=ct[i-1];
for (int j=1,k;(k=prime[j]*i)<=n && j<=cnt;j++){
np[k]=1;
}
}
}//线性筛
int main() {
int T;
bool fg=0;
cin>>T;
int cnt=0;
init(5000000);//初始化,找出五百万以内的素数。
while(T--) {
if (fg) cout<<endl;
fg=1;
int l,r;
cin>>l>>r;
int ans=ct[r]-ct[l];//求区间内有多少个素数
if (!np[l]) ans++;//如果l是一个质数,要给答案加上1,因为上面把l减去了。
printf("[%d,%d] : %d",l,r,ans);
}
return 0;
}
建议
学会预处理,用空间换时间进行优化。
H.玩几个球
先放着。
I.流感
大致题意
在一个 n*n 的方阵中有几个病人,每天会向上下左右四个方向传播,被传播的人在第二天会继续传播,每个病人每天传播一次,问 m 天后有多少病人。
基本思路
模板题,直接用 就能做,想要认真补题的同学可以去学习一下 的基本内容,在此不多赘述。
由于数据范围过小,很多同学用了四层 for 循环暴力求解也过了这道题,但如果 n 变为1000,这种做法就会运行超时。
代码
#include<bits/stdc++.h>
#include<queue>
using namespace std;
typedef long long ll;
const int N=5000005;
struct Node {
int x,y,d;//坐标与天数
};
int dx[4]= {-1,1,0,0};
int dy[4]= {0,0,-1,1};//四个方向。
int main() {
int T;
cin>>T;
while(T--) {
char a[30][30];
int vis[30][30]= {0};//记录得病的病人
int n,m;
cin>>n>>m;
queue<Node> q;
for (int i=1; i<=n; i++) {
for (int j=1; j<=n; j++) {
cin>>a[i][j];
if (a[i][j]=='@') {
q.push({i,j,1});//将病人放入处理队列中。
}
}
}
while(!q.empty()) {
int x=q.front().x,y=q.front().y,d=q.front().d;
q.pop();
vis[x][y]=1;//该房间的人已得病
if (d==m) {
continue;//到第m天之后就不再传播
}
for (int i=0; i<4; i++) {
int tx=x+dx[i],ty=y+dy[i];
if (tx<1 || ty<1 || tx>n || ty>n) continue; //超出边界的处理
if (a[tx][ty]=='#') continue;//空病房的处理
if (vis[tx][ty]==1) continue;//已经得病的病人已经放入了处理队列中,没有必要再处理一遍
q.push({tx,ty,d+1});//将即将得病的人放入处理队列
vis[tx][ty]=1;//下一个房间的人已被感染。
}
}
int ans=0;
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
if (vis[i][j]==1) ans++; //统计病人
}
}
cout<<ans<<endl;
}
return 0;
}
建议
多学习算法知识,如果认真学过 的同学,在看到这题的时候第一反应就会是 的模板题,然而赛时并没有同学用 解决这道题(除了我),对于 208 题已经做完的同学来说这道题是不该用暴力解决的。
J.寻找签到题
大致题意
签到题,给出 n 个气球,找出气球数量最多的颜色并输出该颜色。
基本思路
用 来处理颜色和数量即可。
代码
#include<bits/stdc++.h>
#include<map>
using namespace std;
int main(){
int T;
cin>>T;
bool fg=0;
while(T--){
if (fg) cout<<endl;
fg=1;
int n;
cin>>n;
int a[105]={0};//存各种颜色的气球数量
map<string,int> mp;//给颜色编号
map<int,string> mp1;//使编号指向对应颜色
int cnt=0;//编号
for (int i=1;i<=n;i++){
string s;
cin>>s;
for (int i=0;i<s.size();i++){
if (s[i]>='A' && s[i]<='Z') {
s[i]=(char)(s[i]+32);
}
}
if (!mp.count(s)) {
mp[s]=++cnt;
mp1[cnt]=s;
}
a[mp[s]]++;
}
int mx=0,k=0;
string s1="";
for (int i=1;i<=cnt;i++){
if (k==0 || mx<a[i]){
mx=a[i];
k=i;
s1=mp1[i];
}else if (mx==a[i]){
if (s1>mp1[i]){
mx=a[i];
k=i;
s1=mp1[i];
}
}//找出要求的气球
}
cout<<s1;
}
return 0;
}
建议
在比赛中,有时候简单的题也会放在后面,所以在前面遇到不会做或者写的麻烦的题的时候可以先看后面的题。
K.几桌
大致题意
一共有 n 个人 m 组关系,所有有关系的人坐在同一桌,问一共需要几张桌子。
基本思路
这是一道并查集的模板题,只要利用并查集维护集团关系即可,最终答案便是祖先节点的个数。
代码
#include<bits/stdc++.h>
#include<map>
using namespace std;
const int N=1005;
int p[1005];
int find(int x){
return x==p[x]?x:find(p[x]);
}//找祖先节点
int main(){
int T;
cin>>T;
bool fg=0;
while(T--){
int n,m;
cin>>n>>m;
for (int i=1;i<=n;i++) {
p[i]=i;
}
for (int i=1;i<=m;i++){
int o,u;
cin>>o>>u;
int k=find(o),q=find(u);
if (k!=q){
p[k]=q;//合并并查集
}
}
int ans=0;
for (int i=1;i<=n;i++) {
if (p[i]==i) ans++;
}
cout<<ans<<endl;
}
return 0;
}
建议
并查集是最简单的数据结构之一,在PTA的新生题单中也有相关题目。但是,学习数据结构并不是简单的背模板和抄模板,而是要理解数据结构的构成,原理与作用,之所以提到这个,是因为我们在同学们提交的代码中发现了背模板还背错的现象。
L.去数
大致题意
给出 n 个数,求最少删除多少个数能使得序列变成单调递增序列。
基本思路
我们只需要在原来的 n 个数中找出最长的单调递增序列,即 最长上升子序列(LIS) ,这个序列之外的数的个数就是答案。
可以用 来找最长上升子序列,也可以用 _ 函数找。
下面给出用函数查找的代码。
代码
#include<bits/stdc++.h>
using namespace std;
int main() {
int T;
cin>>T;
while(T--) {
int a[200]={0};
int l[200]={0};//用以记录最长上升子序列的数组
int n;
cin>>n;
for (int i=0; i<n; i++) cin>>a[i];
int len=0;//记录最长上升子序列的长度
l[++len]=a[0];
for (int i=1; i<n; i++) {
if (a[i]>l[len]) {
l[++len]=a[i];
} else {
*lower_bound(l+1,l+1+len,a[i])=a[i];//该函数用来找到l数组中第一个大于a[i]的数的地址。
}
}
cout<<n-len<<endl;
}
return 0;
}
建议
除了语法外,还有很多要学习,C++只是一个开始。
总结
1.新生赛题目并不算难,同学们也取得了优异的成绩,值得表扬。
2.一次比赛并不能代表什么,但是赛后认真补题却可以是自己成长很多。
3.PTA的208题只是将大家引入ACM的大门,未来的路还很长,不能骄傲自满。
4.愿大家的ACM生涯能有一个美好的结局。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!