软件学院集训队第一次选拔赛题解

前言

本套题对大多数同学而言比较偏难,但涵盖了很多算法的基础知识,希望借这套题,让大家体会到算法的学习历程。有些题做不出来,对于你们现在来说是非常正常的,相信大家学习了一段时间后,再看这些题,肯定会感觉非常简单。
算法竞赛中最常用的语言是C++,希望大家能学习一点C++的基础知识,会使用里面的STL库等,这样能够在写题时事半功倍。在做题时,要注意时间复杂度,空间复杂度这些基本的概念,读题时要通过分析数据规模,来找到需要使用的解题方法。除此以外,读题、做题还需要细心,多思考一下极端情况、特殊情况。坑都是一个一个踩过来的,做的题多了,踩得坑多了,后面才能避免犯错。

7-1 签到题中消失的字符

签到题,考察语言基础知识
考察c语言转义字符。\t,\n等这些在C语言中都有特殊的意思,想要输出“\n”需要在前面再加一个\。此外使用printf()还需要注意%,如果想要输出%,还需要在前面再加一个%。

#include <bits/stdc++.h>
using namespace std;
int main(){
	cout << "\\x2333 为\\a什\\b么\\r我\\n签\\n到\\r题\\b都\\a过不了QAQ%";
}

7-2 龟兔赛跑

模拟题,主要考察对细节的处理。
计算乌龟与兔子在n分钟内跑过的路程,最后再比较即可。乌龟跑的路程与时间成正比,为3*n。兔子每跑10分钟会回头看一下,是否超过乌龟,如果超过则会休息30分钟,没超过就会继续跑。用一个循环即可模拟出来。

#include <bits/stdc++.h>
using namespace std;
int main(){
	int n;
	cin >> n;
	// ans1/2:乌龟/兔子在n分钟内分别走过的路程。
	int ans1 = 0 , ans2 = 0;
	ans1 = n * 3;
	// i 表示第i分钟 
	for(int i = 1; i <= n; i++){
		ans2 += 9;
		// 每10分钟看一次,并判断是否超过乌龟。 
		if(i%10 == 0 && ans2 > i*3){
			i += 30;
		} 
	}
	if(ans1 > ans2) cout << "@_@ " << ans1;
	else if( ans2 > ans1) cout << "^_^ " << ans2;
	else cout << "-_- " << ans1;
}

7-3 二进制转十进制

模拟题,考察二进制和编程能力。
模拟二进制转成十进制,需要从后往前开始。

#include <bits/stdc++.h>
using namespace std;
int to_int(string str){
	int ans = 0, f = 1; // f用来记录第i位时,代表的十进制数。
	for(int i = str.size()-1; i >= 0; i--){
		if(str[i] == '1') {
			ans += f;
		}
		f = f*2;
	}
	return ans;
}
int main(){
	string str;
	int s = 0;
	while(cin >> str){
		cout << to_int(str) << endl;
	}
}

7-4 吉良吉影不上班

数论题。 ACM类比赛中的常客,最好多了解一下。
题目的要求其实就是求欧拉函数,在求逆元的时候应用挺多。求单个数的欧拉函数只需要log(n)的时间复杂度。
欧拉函数:就是对于一个正整数n,小于n且和n互质的正整数(包括1)的个数,记作φ(n) 。如果n是质数,则φ(n) = n-1。
欧拉函数求解通式:φ(n)=n*(1-1/p1)(1-1/p2)(1-1/p3)*(1-1/p4)……(1-1/pn)
(pi是n的每一个质因数。具体的证明过程欧拉知道就行,我们只用记结论):

#include <bits/stdc++.h>
using namespace std;
// 求欧拉函数
int phi(int n){
	int ans = n;
	for(int i = 2; i<=sqrt(n); i++){
		if(n%i == 0){
			ans = ans / i*(i-1);
			while(n%i == 0) n/=i;
		}
	}
	if(n > 1) ans = ans / n*(n-1);
	return ans;
}

int main(){
	int n;
	cin >> n;
	cout << phi(n) << endl;
}

7-5 数字三角形

动态规划

在这里插入图片描述
根据题意,我们可以想到一种非常简单粗暴的方法:用DFS从上到下遍历每一条可能得路径。最后寻找最大的结果。需要2的n次方的时间复杂度,题目中的数据范围是1000。很明显会超时。这样做其实遍历了非常多重复的路径,时间上造成很大的浪费。
观察上面图片可以发现,每一个节点都只与他上一行的两个节点相连。比如第4行的第2个数,只与第三行中的第1个数和第2个数相连。即到达当前节点的路径值只会从经过他的上面两个节点的路径值得到。我们只需用一个数组记录当前节点的最大值,一步步向下推,得到的值总是当前节点能经过的最大的值。即求每个节点的最优解,最后得到全体的最优解。
将上面的图形可以转换成下面这个样子。就能很简单的用一个二维数组储存。
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
int dp[1005][1005];  //储存经过当前节点时的路径的最大值。
int arr[1005][1005]; // 储存每个节点的值
int main(){
	int n, ans = -1e9;
	cin >> n;
	// 以 1开始,就不用担心数组越界问题了 
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= i; j++){
			cin >> arr[i][j];
		}
	}
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= i; j++){
			dp[i][j] = max(dp[i-1][j], dp[i-1][j-1]) + arr[i][j];
			ans = max(ans, dp[i][j]); 
		}
	}
	cout << ans;
}

题目的数据都是非负的,如果节点数据是负数,上面的代码就需要修改,感兴趣的同学可以思考一下怎么修改,此外还能将空间复杂度从n^2优化到n,这里就不过多说了。

7-6 月饼

模拟题,主要考察结构体排序,贪心。
根据题意,我们可以用贪心来解, 优先卖单位价值最高的物品。跟据这个条件从大到小排好序后依次选择即可。注意要用double。 代码中用到了一个cmp函数,用来规定排序的规则。 非常好用, 希望大家去了解一下。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct node{
	double n, v;
}arr[1005];

// 排序函数, 根据这种规则排序 
bool cmp(node a, node b){
	return a.v/a.n > b.v/b.n;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	int n, m;
	cin >> n >> m;    // m:市场需求量 
	for(int i= 0; i<n; i++){
		cin >> arr[i].n;
	}
	for(int i= 0; i<n; i++){
		cin >> arr[i].v;
	}
	sort(arr,arr+n, cmp);
	double ans = 0;  // 答案 
	int f = 0;       // 记录数组的下标 
	while(m>0 && f<n){
		if(m >= arr[f].n){
			ans = ans+arr[f].v;
			m = m-arr[f].n;
			f++;
		}else{
			ans = ans + arr[f].v/arr[f].n*m;
			break;
		}
	} 
	printf("%.2lf",ans);
} 

7-7 搜搜搜索

搜索题,考察dfs、bfs。
非常简单的搜索题,给一个起点,找最大的陆地面积,需要注意:题中给出要求只有上下左右的点连在一块才能算一块陆地,斜方向不算。只需遍历一遍就行,因此用dfs,bfs都可以(注意要用一个数组标记是否已经搜索过了)。

#include <bits/stdc++.h>
using namespace std;

char arr[105][105]; // 储存地图
int dis[105][105]; // 用来标记是否已经遍历过了
int d[4] = {1,-1,0,0};
int f[4] = {0,0,1,-1}; // 控制遍历的方向
int n, m, bx, by, ans = 0;
void dfs(int bx, int by){
	ans++;
	dis[bx][by] = 1;
	for(int i = 0; i < 4; i++){
		int x = bx+d[i], y = by+f[i];
		if(dis[x][y] == 0 && arr[x][y] != '0' && x>=1 && x<=n && y>=1 && y<=m){
			dfs(x,y);
		}
	}
}

int main(){
	cin >> n >> m >> bx >> by;
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= m; j++){
			cin >> arr[i][j];
		}
	}
	dfs(bx, by);
	cout << ans;
}

7-8 遍遍遍历

数据结构题
二叉树的遍历,涉及到数据结构的基础知识,给定前序和中序遍历,输出后序遍历。需要了解二叉树,以及前序、中序、后序遍历的知识。除了天梯赛,一般不会单独考,感兴趣可以多学学。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

char q[1000];
char z[1000];
map<char, int> mp; //用map记录中序遍历每个字符的位置,就不用每次寻找了。

void build(int now, int ml, int mr){
	if(ml > mr) return;
	int f = mp[q[now]];
	build(now+1, ml, f-1);
	build(now+(f-ml+1), f+1, mr);
	cout << q[now];  //输出后序遍历 
}

int main(){
	int n;
	cin >> n;
	for(int i = 1; i<=n; i++) cin >>q[i];
	for(int i = 1; i<=n; i++) {
		cin >> z[i];
		mp[z[i]] = i;
	} 
	build(1, 1, n);
}

7-9 群群群消息

非常经典的一道贪心题。
由于n的数据范围是100000,所以肯定不能用n方的复杂度的方法来写,自然而然能够想到贪心。
仔细分析一下就可以发现,他能将任意连续范围的消息长度都增加一,所以只需要寻找后一条消息长度减前一条消息长度大于0的和就行(简单说就是找arr[i-1]<arr[i]的数就行)。为了防止最长的消息在中间或者前边出现,还需要把最长的消息的长度添加到数组最后面。
比如2 3 5 4 6 3,
为了方便理解, 将长度最长的6加到数组后面为 2 3 5 4 6 3 6
第一次变为 3 3 5 4 6 3 6
第二次变为 4 4 5 4 6 3 6
第三次变为 5 5 5 4 6 3 6
第四次变为 5 5 5 5 6 3 6
第五次变为 6 6 6 6 6 3 6
第六次变为 6 6 6 6 6 4 6
第七次变为 6 6 6 6 6 5 6
第八次变为 6 6 6 6 6 6 6

#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
long long arr[N], n, t;
int main()
{
    //关闭流同步,能使输入输出更快
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> t;
	while(t--){
		long long  ans = 0, maxn = 0;
  	  	cin >> n;
    	for(int i=1;i<=n;i++){
    		cin >> arr[i];
    		maxn = max(arr[i], maxn);
		} 
		arr[n+1] = maxn;
    	for(int i=2;i<=n+1;i++){
		    if(arr[i] > arr[i-1]){
      	      	ans += arr[i]-arr[i-1];			
			} 
		} 
   		cout<< ans << endl;
	}
    return 0;
}

7-10 秀恩爱分得快

模拟题,考编程能力、并查集等
一道大模拟,思路不算复杂, 实现的过程很麻烦, 需要注意读清题意,
注意题中-0与0是有区别的,所以要用字符串输入。直接上代码劝退……

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
double fa[1100][2100]; //   fa[i][j]储存 在第i张照片中 编号为j的人,亲密度是多少 
// v1 v2 分别储存 情侣a,b的亲密度最高的人 
vector<int> v1, v2;
int n, m, k;

// 将编号转换成题目要求的输出方式,并输出。 
void print(int a, int b){
	if( a == 0) cout << "-0";
	else if(a >= 1000) cout << a-1000;
	else cout << "-" << a;
	cout << " ";
	if( b == 0) cout << "-0";
	else if(b >= 1000) cout << b-1000;
	else cout << "-" << b;
	cout << endl;
}

// 对输入的字符串进行处理 ,返回一个对应编号 
// 女性在[0-1000), 男性在[1000,2000); 
int to_int(string str){
	int ans = 0, f = 1;
	for(int i = str.size()-1 ; i>=0; i--){
		if(str[i] != '-'){
			ans = ans + (str[i]-'0')*f;
			f *= 10;
		}
	}
	if(str[0] != '-') ans += 1000;
	return ans;
}

// 寻找 a的亲密对最高的人 
vector<int> jud(int a, int b, double ans1){
	// 根据a的性别规定,循环的范围 
	int l, r;
	if(a < 1000) {
		l = 1000; r = 2000;
	}else{
		l = 0; r = 1000;
	}
	vector<int> v;
	int sign = 0;
	for(int i = l; i < r; i++){
		double ans2 = 0;
		for(int j = 1; j <= m; j++){
			if(fa[j][i] == fa[j][a])
			ans2 += fa[j][i];
		}
		if(ans2 > ans1){
			v.clear();
			v.push_back(i);
			ans1 = ans2; 
		}else if(fabs(ans1-ans2) < 1e-6){
			v.push_back(i);	
		}
	}
	return v; 
}


// 检查这一对情侣的亲密度情况,分情况输出 
void check(int a, int b){
	int sign = 0;
	for(int i = 0; i<v1.size(); i++){
		if(v1[i] == b) sign = 1;
	}
	for(int i = 0; i<v2.size(); i++){
		if(sign == 1 && v2[i] == a) sign = 2;
	}
	if(sign == 2){
		print(a,b);
	}else{
		for(int i = 0; i<v1.size(); i++)
			print(a, v1[i]);
		for(int i = 0; i<v2.size(); i++)
			print(b, v2[i]);
	}
}

int main(){
	string s;
	cin >> n >> m;
	for(int i = 1; i<=m; i++){
		cin >> k;
		double v = 1.0/k;  // 计算照片中的亲密度 
		for(int j = 1; j <= k; j++){
			cin >> s; 
			int t = to_int(s);
			fa[i][t] = v;
		}
	}
	
	string s1, s2;
	cin >> s1 >> s2;
	int a = to_int(s1); int b = to_int(s2);
	// ans1  这一对情侣的亲密度 
	double ans1 = 0;
	for(int i = 1; i <= m; i++){
		if(fa[i][a] == fa[i][b])
			ans1 += fa[i][a];
	}
	v1 = jud(a, b, ans1);
	v2 = jud(b, a, ans1);	
	check(a, b);
}
  • 9
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值