CSDN竞赛15期题解

总结

CSDN竞赛越来越频繁了,这次竞赛比较简单,就当练习题了。

题目列表

1.求并集

题目描述

由小到大输出两个单向有序链表的并集 如链表 A {1 -> 2 -> 5 -> 7} 链表 B {3 -> 5 -> 7 -> 8} 输出: {1 -> 2 ->3 - > 5 -> 7 ->8} 。

输入描述:

第一行输入整数n,m,(1<=n,m<=1000)分别表示两个链表的长度。
第二行给出A链表所包含元素。(1<=a<=1000)
第三行给出B链表所包含元素。(1<=b<=1000)

输出描述:

按题目描述输出。

输入样例:

4 4
1 2 5 7
3 5 7 8

输出样例:

1 -> 2 ->3 -> 5 -> 7 ->8

分析

这道题作为面试题,考察链表比较合适,作为笔试题不太合适。一般的做法是使用类似于归并排序的合并算法,使用双指针来合并。但是完全没必要这么做,直接用一个数组读入,然后排下序就可以了。

当然,这题的用例告诉我们需要去重,所以可以读入一个set中,然后再排序就可以了。而实际上set已经默认排好序了,直接输出就可以了。

代码

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>
#include <set>
using namespace std;
set<int> s;
int main() {
	int n;
	int m;
	cin>>n>>m;
	vector<int> a;
	int x;
	for(int i = 0; i < n + m; i++) {
		cin>>x;
		s.insert(x);
	}
	for(auto t : s) {
		a.push_back(t);
	}
	int q = a.size();
	for(int i = 0; i < q - 1; i++) {
		cout<<a[i]<<" -> ";
	}
	cout<<a[q - 1]<<endl;
	return 0;
}

2.小T找糖豆

题目描述

已知连续序列A,包含1e18个元素,分别是[1,1e8]。 现在去除序列中的n个元素. 得到新的连续序列[X,1e8],该序列中 最小元素是?

分析

本题的题意是去掉若干元素后,求剩下连续序列的最小元素,用例给我们的感觉是必须是最后一个连续序列,所以相当于求出去除序列的最大值,加上1就是连续序列的最小元素了。

代码

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>
using namespace std;
int solution(int n, std::vector<int>& vec) {
	int res = 0;
	for(auto x : vec) {
		res = max(res, x);
	}
	return res + 1;
}
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;
}

3. 争风吃醋的豚鼠

题目描述

N个节点两两建边。 不存在3个节点相互之前全部相连(3个节点连接成环) 。最多能建立多少条边?

输入描述:

输入整数n.(1<=n<=1e5)

输出描述:

输出最大边数

输入样例:

4

输出样例:

4

分析

读完题目就知道肯定是有一个公式的,代码简洁,这类题目作为算法题不太合适,有点纯数学了。考试时想的是类似于二分图。我们将节点分为两组,两组节点之间互相全部连接上,组内节点不连接,这样添加任意一条边都会形成环,保证了边的最大值。

比如 a , b , c , d a,b,c,d a,b,c,d四个点, a , b a,b a,b作为一组, c , d c,d c,d作为一组,连接 a c ,   a d ,   b c ,   b d ac,\ ad,\ bc,\ bd ac, ad, bc, bd,构成的四条边不会形成环,同时边数是最大的。所以遍历下将 n n n个节点划为两组节点的所有划法,取其中的边数最大值就可以了。

代码

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
int main() {
	int n;
	std::cin>>n;
	ll res = 0;
	for(int i = 1; i < n; i++) {
		res = max(res,(ll)i * (n - i));
	}
	std::cout<<res<<std::endl;
	return 0;
}

4.题目名称:选择客栈

题目描述

丽江河边有 n 家很有特色的客栈,客栈按照其位置顺序从 1 到 n 编号。每家客栈都按照某一种色调进行装饰(总共 k 种,用整数 0 ~ k-1 表示),且每家客栈都设有一家咖啡店,每家咖啡店均有各自的最低消费。 两位游客一起去丽江旅 游,他们喜欢相同的色调,又想尝试两个不同的客栈,因此决定分别住在色调相同的两家客栈中。晚上,他们打算选择一 家咖啡店喝咖啡,要求咖啡店位于两人住的两家客栈之间(包括他们住的客栈),且咖啡店的最低消费不超过 p 。 他们想 知道总共有多少种选择住宿的方案,保证晚上可以找到一家最低消费不超过 p 元的咖啡店小聚。

输入描述:

共n+1 行。
第一行三个整数 n ,k ,p ,每两个整数之间用一个空格隔开,分别表示客栈的个数,色调的数目和能接受的最低消费的最高值;
接下来的 n 行,第 i+1 行两个整数,之间用一个空格隔开,分别表示 i 号客栈的装饰色调和 i 号客栈的咖啡店的最低消费。

输出描述:

一个整数,表示可选的住宿方案的总数。

输入样例:

5 2 3
0 5
1 3
0 2
1 4
1 5

输出样例:

3

分析

如果说前面题目出的不像算法题,那么这最后一题就有点坑了,第一个坑在于不给数据范围,但凡审题的同学认真点都不会不给数据范围,这种题目不给数据范围的话,我们是不方便选择合适复杂度的算法来求解的。

首先要考虑的是选择的两个客栈中间有没有合适的咖啡店,我们固然可以通过遍历来判断,但是复杂度高。这里可以使用前缀和来优化下,咖啡店的最低消费具体是多少我们不关注,如果不超过p,就是1,表示可以选择;超过了p,就是0,不能选择。然后对这个由0和1构成的数组求下前缀和s,就可以在O(1)的时间内判断两个客栈之间有没有合适的咖啡店了。

客栈 i i i j j j之间合适的咖啡店数是 s [ j ] − s [ i − 1 ] s[j]-s[i-1] s[j]s[i1],只要这个值不是0,就表示我们可以选择 i , j i,j i,j两个客栈住宿。

暴力的做法是二重循环枚举 i , j i,j i,j,只要两家客栈色调相同,并且之间有合适的咖啡店,方案数就加一。时间复杂度是 O ( n 2 ) O(n^2) O(n2),由于题目没有给定数据范围,所以可以提交下看看,发现TLE了,这就是本题的第二个坑,因为如果你在代码里面加上如果 n > 1 e 4 n>1e4 n>1e4的代码再提交就可以通过九成的用例了。这题用例的评测是一起了,最后一个用例TLE了,就一分没有,这点很不科学。

纯暴力的解法复杂度是立方级别的,前面使用前缀和优化掉一重循环,既然TLE了还需要继续优化。色调一共 k k k种,虽然没有给数据范围,但是根据经验可以判定 n n n的范围至少是 1 0 5 10^5 105级别的,而 k k k的范围一般不大。我们将所有的客栈根据色调不同按顺序分为 k k k组,这样同一组内的客栈我们就不需要再考虑色调,只需要关心有没有合适的咖啡店就行了。问题就变成了在一个线性序列中,找到点对使得两点之间有合适的咖啡店了。

这里可以发现问题的单调性,比如一个人住在第一家客栈,第二个人从第二家客栈开始遍历,直到两家客栈之间有合适的咖啡店位置,从这家咖啡店到后面的所有客栈都可以住第二个人。然后第一个人住第二家客栈,第二个人不需要从第三家客栈遍历起,只需要从上次找到合适咖啡店的位置开始遍历就可以了,因为上次走过的客栈肯定都没有合适的咖啡店。这样一来,第二个人至多走完客栈一遍。如果某个色调序列的长度是q,那么在这个色调里面找两个合适客栈的复杂度就是 O ( q ) O(q) O(q),遍历完k个色调序列的总的复杂度就是 O ( n ) O(n) O(n)

不考虑本题的数据范围和用例评测的坑,这题考察了前缀和和双指针算法,一步步优化时间复杂度,题目质量还是不错的。

代码

#include <iostream>
#include <string>
#include <sstream>
#include <vector>
using namespace std;
int main() {
	int n;
	int k;
	int p;
	cin>>n>>k>>p;
	vector<int> c(n+1), a(n+1), s(n+1, 0);
	vector<int> m[k];
	for(int i = 1; i <= n; i++) {
		cin>>c[i]>>a[i];
		if (a[i] > p) a[i] = 0;
		else a[i] = 1;
		s[i] = s[i-1] + a[i];
		m[c[i]].push_back(i);
	}
	int res = 0;
	for(int t = 0; t < k; t++) {
		int q = m[t].size();
		for(int i = 0,j = 0; i < q; i++) {
			if(a[m[t][i]]) {
				res += q - i - 1;
				continue;
			} else {
				if(j > i) {
					res += q - j;
					continue;
				} else j = i + 1;
				while(j < q && !(s[m[t][j]] - s[m[t][i]-1])) j++;
				if(j < q) {
					res += q - j;
				} else {
					break;
				}
			}
		}
	}
	cout<<res<<endl;
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值