CSDN竞赛6期题解

CSDN编程竞赛报名地址:https://edu.csdn.net/contest/detail/16
(请不要删掉此地址)

总结

这次竞赛题目比较简单,没多大必要写题解。更多的还是给出自己的一些体会和建议吧。

很多同学已经对比赛规则和编程体验给出了建议,比如复制粘贴自己在当前页面写的代码也计算粘贴次数很不合理,遇见for循环或者名称较长的变量不能复制只能手敲体验不太好;题目给出了标准输入的语句,但是给出的solution接口远不如leetcode的接口友好,比如答案需要用long long表示,给出的模板函数返回值还是int,容易给人误导。再比如调试需要完成等待几秒才能再次调试或者提交,以及调试过程中的warning信息会参与结果的比对等等。我主要的建议还是在命题方面。

作为大规模的竞赛,这次竞赛的命题真的很不用心。竞赛一般都是出的新题,而这次比赛直接从之前csdn的每日一题中随机抽了几题,而且难度也没有控制好,题目过于简单没有区分度。而且题目的描述不够详细,用例不够详细。

我的建议是:找个有算法竞赛背景命题人出新题,而不是随便在题库里抽题,才能保证公平性。命题完成需要另外的人进行审核,题目语义是否准确,用例是否充分,难度设置是否合理。都是需要review的地方,不是一两句话概括题意,用例随便出个简单的,连基本输入格式都没有覆盖到,每道题至少出三个用例,覆盖不同的情况。下面结合具体题目来说下具体有哪些需要改进的地方吧。

题目列表

1.严查枪火

题目描述

X国最近开始严管枪火。 像是“ak”,“m4a1”,“skr”。都是明令禁止的。 现在小Q查获了一批违禁物品其中部分是枪支。
小Q想知道自己需要按照私藏枪火来关押多少人。 (只有以上三种枪被视为违法)

分析

作为简单的模拟题,如果给出了清晰的接口,是可以在两分钟内ac本题的,但是比赛时WA了几次才通过这题。因为题目没有对输入格式清晰地描述出来,这也与一开始我选择删掉题目的输入模板自己写输入有关,如果选择自己写输入的同学,就会发现题目对输入的描述有多么模糊了。

题目首先给定一个输入n,接下来输入n行字符串表示每个人携带的物品,问最后要关押多少人。题目唯一给的用例是一行一个字符串的情况,那么对于题意就有几种理解了:

  • 1.输入n个字符串,判断这n个字符串中包含违禁物品的有几个。
  • 2.输入n行字符串,每行字符串是连续的,通过对每行字符串的子串是否包含违禁品来判断是否应该关押。
  • 3.输入n行字符串,每行字符串由多个按空格隔开的字符串表示,表示每个人携带的若干件物品的名字,只要携带了违禁品就关押。

显然,根据题意是无法判断题目输入到底是什么格式的,只能不断的提交,一种情况一种情况的尝试,直至通过本题。而最终题目后台的输入格式是第三种情况,我觉得要么题目描述清楚,每行表示每个人携带的若干件物品,输入用空格隔开;要么增加几个用例来体现输入的格式,而不是这样模糊的描述。

最后,自己写输入要注意的是,读取n后需要getchar()来去掉n后面的回车,否则getline读取的第一个字符串就是回车字符。

代码

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
string t[3] = {"ak", "m4a1", "skr"};
bool check(string &s) {
	for(int i = 0;i < 3;i++) {
		if(s == t[i]) {
			return true;
		}
	}
	return false;
}
int main() {
	int n;
	std::vector<std::vector<std::string>> vec;
	std::cin>>n;
	string s;
	int res = 0;
	getchar();
	for(int i = 0;i < n;i++) {
		getline(cin,s);
		stringstream ss(s);
		string x;
		while(ss>>x) {
			if(check(x)) {
				res++;
				break;
			}
		}
	}
	std::cout<<res<<std::endl;
	return 0;
}

2.鬼画符门

题目描述

鬼画符门,每年都会统计自己宗门鬼画符消耗的数量,往年一直是大师兄管理, 但是这次鬼艺接手了, 你能帮鬼艺写一个
程序统计每年消耗数量最多的鬼画符吗?

分析

本题考查hash的简单应用。第一遍遍历字符串数组统计出每个字符串出现的次数,同时更新下出现最多的鬼画符次数。第二次遍历字符串数组,一旦遍历到某个鬼画符出现的次数等于最多的次数,返回该字符串即可。

代码

#include <iostream>
#include <string>
#include <sstream>
#include <unordered_map>
#include <algorithm>
#include <vector>
using namespace std;
unordered_map<string,int> m;
int main() {
	int n;
	std::cin>>n;
	vector<string> t;
	string s,res;
	for(int i = 0;i < n;i++) {
		cin>>s;
		t.push_back(s);
	}
	int p = 0;
	for(auto x : t) {
		m[x]++;
		p = max(p, m[x]);
	}
	for(auto x : t) {
		if(m[x] == p) {
			res = x;
			break;
		}
	}
	cout<<res<<endl;
	return 0;
}

3.收件邮箱

题目描述

已知字符串str,str表示邮箱的不标准格式。 其中”.”会被记录成”dot”,”@”记录成”at”。 写一个程序将str转化成可用
的邮箱格式。(可用格式中字符串中除了开头结尾所有”dot”,都会被转换,”at”只会被转化一次,开头结尾的不转化)

分析

这题也是之前每日一题里面的题目,稍微需要多一点判断的模拟题,考察对字符串的处理。

可以用res来记录最终的结果。逐位遍历str,然后进行判断:

  • 如果str[i]是‘d’,如果i不是在开头,也不是在str的倒数第三位,并且后两位是‘o’和’t‘,那么res需要追加上’.'。
  • 如果str[i]是’a’,还是要判断是否在开头或者结尾,不在的话后面的字符是’t’,并且之前没有转化过才给res追加上’@'。
  • 如果str[i]不是‘a’或者’d’,或者是前两种分支的else情况,直接追加到res末尾即可。

本题对字符串的模拟并不复杂,主要需要细心的地方是判断后面的字符时需要注意下标不要越界,以及判断为dot或者at后需要跳几位再继续遍历,最后就是在考虑到at只转化一次的基础上注意开头的at不转化的规则。

代码

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
std::string solution(std::string str){
	std::string res;
	int n = str.size();
	bool flag = true;
	for(int i = 0;i < n;i++) {
		if(str[i] == 'd') {
			if(i && i < n - 3 && str[i+1]=='o' && str[i+2]=='t') {
				res += ".";
				i += 2;
			}
			else res += "d";
		}
		else if(str[i] == 'a' && flag) {
			if(i && i < n - 2 && str[i+1] == 't') {
				res += "@";
				flag = false;
				i++;
			}
			else res += "a";
		}
		else res += str[i];
	}
	return res;
}
int main() {
	std::string str;
	std::cin>>str;
	std::string result = solution(str);
	std::cout<<result<<std::endl;
	return 0;
}

4.最长递增的区间长度

题目描述

给一个无序数组,求最长递增的区间长度。如:[5,2,3,8,1,9] 最长区间 2,3,8 长度为 3

分析

这题也是每日一题的题目,第一次看见这题还以为用例有问题,因为第一印象是求最长递增子序列,然后觉得用例的最初递增子序列是2,3,8,9,长度是4。写了个LIS的代码提交上去WA了。再次读题发现求的是最长连续递增子序列。

一般算法题里不连续的叫做子序列,连续的叫做子串或者连续子序列,看见区间就没有反应过来。既然是求最长连续子序列长度,那么解法就比LIS问题更简单了。不用去考虑双指针或者DP的思想,直接模拟解决即可。

设res为当前遍历过的序列的最长连续递增子序列的长度,cnt是当前遍历到的最长连续递增子序列的长度,或者说是以当前遍历到的元素为末尾的最长连续递增子序列的长度。具体遍历过程就是:遍历当前元素,如果当前元素大于前一个元素,cnt++,否则cnt置为1表示以当前元素结尾的最长连续递增子序列的长度是1。每次cnt置1的时候以及遍历结束的时候尝试更新下最后的结果res即可。

遍历过程中蕴含DP的思想,即f[i]表示以第i个元素结尾的最长连续递增子序列的长度,最终的答案res = max(f[i])。由于在遍历过程中就更新了res,所以dp数组简化为了变量cnt。当然,写代码时候不用考虑背后的DP思想,也是可以很快写出来的。

代码

#include <iostream>
#include <algorithm>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
const int N = 10005;
int f[N];
int solution(int n, std::vector<int>& vec){
	int res = 1;
	int cnt = 1;
	for(int i = 0;i < n;i++) {
		if(i && vec[i] > vec[i - 1]) cnt++;
		else {
			res = max(res,cnt);
			cnt = 1;
		}
	}
	res = max(res,cnt);
	return res;
}
int main() {
	int n;
	std::vector<int> vec;
	std::cin>>n;
	std::string line_0, token_0;
	getline(std::cin >> std::ws,line_0);
	std::stringstream tokens_0(line_0);
	while(std::getline(tokens_0, token_0, ' ')){
		vec.push_back(std::stoi(token_0));
	}
	int result = solution(n,vec);
	std::cout<<result<<std::endl;
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值