总结
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[i−1],只要这个值不是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;
}