动态线条
动态线条end

绍大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 的数据范围非常小(只有100),所以只需要枚举每个商店作为起点,计算总路程并统计最短路程输出即可。

代码

#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 种签子,给定每种签子的数量,价格和折扣,计算签子总数和总价格。

基本思路

直接求和即可,由于折扣会导致价格出现小数,所以要用 double 类型的变量表示价格。

代码

#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 的柚子,双方轮流进行操作(俩人都采用最优策略),每次操作可以拿走 一个质数的幂次个数 的柚子(即 pk p), 最后拿走柚子的人获胜。给定一个 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 (0kw),使得 k 在加上这 n 个整数时始终保持(0kw)。

基本思路

由于本题的 w 的数据范围为 (1w109),且有多组数据,所以不能采用暴力枚举的方法 (从 1 枚举到 w) 来求 k 的个数。

我们首先对每个整数 a[i] 求他的前缀和 (即j=1ia[j]) ,然后找出前缀和中的最大值和最小值。

如果前缀和的最大值或最小值的绝对值超过了 w ,则当 k 加到 最大值或最小值位置 时,一定会出现 k <0 或 k>w 的情况,则该 k 不满足要求。

之后的情况,我们用mx表示前缀和最大值,用mn表示最小值,分三种情况讨论:

1 : 当 mn0 时,若用 (lr) 表示 k 的答案区间,则 l 可以取到 0 ,而 r 不能超过 wmx ,所以一共有 rl+1 个 k 可取。

2:当 mx0 时, r 可以取到 w ,但 l 不能小于 (|mn|) 所以 l=|mn| , 一共有 rl+1 个 k 可取。

3:当 mx0mn0 时,l 不能小于 (|mn|) 而 r 不能超过 wmx ,所以 l=|mn|r=wmx

一共有 rl+1 个 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.数一数素数

大致题意

给定一个区间 (l,r) ,输出该区间内的素数个数。

基本思路

由于该题数据范围只有五百万,我们完全可以将五百万以内的素数全部筛选出来,所以这题主要考虑的是筛选素数的方法。

筛选素数的方法一般有俩种,一种是埃式筛法,另一种是线性筛,俩种筛法的区别在于筛选的方式不同,从而导致效率也不同(线性筛会快很多),想要了解线性筛,可以点这里

但是不管用哪一种筛法,我们都只需要筛选一次,即在回答询问前,先将五百万以内的素数筛选并记录当前位置素数个数,很多同学在比赛时超时的原因是对于每一次询问,都进行了一次筛选,这就造成了时间上的冗余,增加了时间复杂度。

筛出素数后,我们只要记录到当前位置有多少个素数即可,想要知道区间内有多少个素数,只要用俩个前缀和相减的方法来求区间和即可。

由于大家已经学习过埃式筛法,这里给出的代码采用的线性筛的方法。

代码

#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 天后有多少病人。

基本思路

BFS 模板题,直接用 BFS 就能做,想要认真补题的同学可以去学习一下 BFS广 的基本内容,在此不多赘述。

由于数据范围过小,很多同学用了四层 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;
}

建议

多学习算法知识,如果认真学过 BFS 的同学,在看到这题的时候第一反应就会是 BFS 的模板题,然而赛时并没有同学用 BFS 解决这道题(除了我),对于 PTA 208 题已经做完的同学来说这道题是不该用暴力解决的。

J.寻找签到题

大致题意

签到题,给出 n 个气球,找出气球数量最多的颜色并输出该颜色。

基本思路

map 来处理颜色和数量即可。

代码

#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) ,这个序列之外的数的个数就是答案。

可以用 DP 来找最长上升子序列,也可以用 lower_bound 函数找。

下面给出用函数查找的代码。

代码

#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生涯能有一个美好的结局。

posted @   冘木  阅读(586)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
Live2D
欢迎阅读『绍大2022级ACM集训队新生选拔赛题解(更新中)』
动态线条
动态线条end
点击右上角即可分享
微信分享提示