普及冲奖——STL2补题报告

我不想写可恶的补题报告!!!

T1.锯齿矩阵(例题)

一.题目要求

锯齿矩阵是指每一行包含的元素个数不一定相同的矩阵,比如:

  1. 3 5 2 6 1
  2. 2 3 4
  3. 1 6 2 7
  4. 2 3 5

初始时矩阵为空,读入𝑚 对整数 (𝑥,𝑦),表示在第 𝑥行的末尾加上一个元素 𝑦。

输出最终的锯齿矩阵。

输入描述

第一行:输入两个正整数n和m,n表示锯齿矩阵的行数,m表示插入次数。

接下来m行:每行输入两个整数x和y,表示在第x行的末尾插入一个元素y。

输出描述

一共输出 n 行,每行若干个用空格分隔的整数。如果某行没有任何元素,则输出一个空行

输入样例
  1. 3 12
  2. 1 3
  3. 2 2
  4. 2 3
  5. 2 4
  6. 3 1
  7. 3 6
  8. 1 5
  9. 1 2
  10. 1 6
  11. 3 2
  12. 3 7
  13. 1 1
输出样例
  1. 3 5 2 6 1
  2. 2 3 4
  3. 1 6 2 7
数据范围

对于100%的数据:1≤𝑛,𝑚≤1e5,1≤𝑥≤𝑛,0≤𝑦≤1e5

二.解题思路

  1. ​初始化存储结构​​:我们需要一个存储每行数据的结构。由于行数固定为n(从1到n),我们可以创建一个长度为n+1的数组(或列表),每个元素是一个动态数组(如C++的vector)来存储该行的元素。

  2. ​处理插入操作​​:对于每次插入操作(共m次),读取行号x和要添加的值y,然后将y添加到第x行的动态数组末尾,这个操作的时间复杂度是O(1)。

  3. ​输出结果​​:遍历每一行(从1到n),对于每一行,如果该行的动态数组不为空,则输出所有元素(用空格分隔);如果为空,则输出一个空行。

三.代码

  老师代码:

#include<iostream>
#include<vector>
using namespace std;
const int N=1e5+20;
long long n,m;
vector<int>ve[N];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int x,y;
		cin>>x>>y;
		ve[x].push_back(y);
	}
	for(int i=1;i<=n;i++){
		for(int j=0;j<ve[i].size();j++) cout<<ve[i][j]<<" ";
		cout<<"\n";
	}
	return 0;
}

T2.数字去重(例题)

一.题目描述

给出一个包含 n 个元素的数组 a ,去掉 a 中重复的数字并从小到大排序输出。

输入描述

第一行:输入一个正整数n,表示数组元素个数。

第二行:输入n个整数,表示a中的元素。

输出描述

输出数组a去重且从小到大排序之后的结果。

输入样例
  1. 5
  2. 10 8 7 8 10
输出样例
  1. 7
  2. 8
  3. 10
数据范围

对于100%的数据:1≤𝑛≤1e5,−109≤𝑎𝑖≤1e9

二.解题思路

  1. ​读取输入数据​​:

    • 读取数组元素个数n

    • 读取包含n个整数的数组

  2. ​高效去重操作​​:

    • 使用集合(set)数据结构自动去重

    • 集合的特性:元素唯一,但无序

  3. ​排序操作​​:

    • 将去重后的集合转换为列表

    • 对列表进行升序排序

  4. ​输出结果​​:

    • 遍历排序后的列表

    • 每个元素单独一行输出

三.代码

老师代码:

#include<iostream>
#include<set>
using namespace std;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	int n;
	cin>>n;
	set<int>s;
	for(int i=0;i<n;i++){
		int x;
		cin>>x;
		s.insert(x);
	}
	set<int>::iterator it;
	for(it=s.begin();it!=s.end();it++){
		cout<<*it<<"\n";
	}
	return 0;
}

T3.运动会

一.题目要求

小可小时候是一个学生干部。有一次,他们年级举办运动会,有n个运动员参加比赛,编号依次为1…n。编号为i的运动员的班级是ci班。

经过一场场比赛,每个运动员都有自己的比赛成绩,编号为i的运动员的比赛成绩是bi分,众所周知,运动员成绩越高越厉害。

比赛结束后,班主任向小可提出了m个问题,如果小可都能回答,那么就证明小可工作干得不错,下次还让他当学生干部。班主任提出的m个问题都以c,k的形式给出,表示班主任想知道班级c的同学中成绩排名第k名的学生编号(当然成绩最高的是第一名)是多少(也就是1-n中的一个数)

同时,运动会保证了一个班级中不可能出现比赛成绩一样的学生,且班主任问的问题一定是有解的。

输入描述

第一行:一个数n ,表示运动员的数量

第2~n+1行:每行两个数字c b,表示运动员所在班级编号和比赛成绩(对应编号1到n的运动员)

第n+2行:一个数m,表示班主任题的m次询问

第n+3~n+m+2行:每行两个数字c k,表示询问班级c比赛成绩第k名学生的编号为多少

输出描述

共m行:输出一个数字,表示对应询问的答案

输入样例
  1. 6
  2. 1 5
  3. 1 2
  4. 1 8
  5. 2 3
  6. 2 9
  7. 3 1
  8. 4
  9. 1 2
  10. 1 3
  11. 2 2
  12. 3 1
输出样例
  1. 1
  2. 2
  3. 4
  4. 6
数据范围

对于100%的数据:1≤𝑛≤1e3,1≤𝑐≤10,1≤𝑏≤1e9,1≤𝑚≤100

二.解题思路

题目要求处理多个查询,每个查询要求输出指定班级中成绩排名第k的学生编号。数据规模如下:

  • 运动员数量 n≤103(即最多1000人)

  • 班级编号 c范围在 1 到 1e3(最多10个班级)

  • 查询数量 m≤100

由于班级数量很少(最多10个),并且每个查询要求班级内部的成绩排名,核心思路是​​为每个班级预计算并存储一个按成绩排序的学生列表​​。这样,每个查询都可以在常数时间内完成。

三.代码

老师代码:

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
struct node{
	int f,id;
};
bool cmp(node a,node b){
	return a.f>b.f;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	int n,m,c,b,k;
	cin>>n;
	vector<vector<node> >a(n+1);
	for(int i=1;i<=n;i++){
		cin>>c>>b;
		a[c].push_back({b,i});
	}
	for(int i=1;i<=n;i++){
		sort(a[i].begin(),a[i].end(),cmp);
	}
	cin>>m;
	while(m--){
		cin>>c>>k;
		cout<<a[c][k-1].id<<"\n";
	}
	return 0;
}

T4.集合相似度

一.题目要求

最近小可在学习集合,于是他提出了很多有关集合的问题,其中的一个问题是给你两个集合(集合内部没有重复的元素),让你求集合的相似度是多少,集合的相似度定义如下:

2 个集合的相似度 = 100 * 相同元素的个数/(相同元素个数+不同元素个数)

聪明的你可以帮助小可解决这个问题吗?

输入描述

多组输入:第一行输入一个正整数t,表示样例组数。

对于每组数据:

第一行:输入两个正整数n和m,表示两个数组中元素个数。

第二行:输入n个整数,表示第一个集合内的元素 𝑎𝑖a​i​​。

第三行:输入m个整数,表示第二个集合内的元素 𝑏𝑖b​i​​。

输出描述

对于每一组数据,输出一行一个答案,表示两个集合的相似度。

输入样例
  1. 2
  2. 2 3
  3. 1 2
  4. 2 3 4
  5. 3 3
  6. 5 3 4
  7. 3 4 1
输出样例
  1. 25
  2. 50
数据范围

对于100%的数据:1≤𝑡≤10,1≤𝑛,𝑚≤1e5,0≤𝑎𝑖,𝑏𝑖≤2∗1e5


二.解题思路

  1. ​数据预处理​​:

    将输入的列表转换为集合(set),自动去除重复元素,确保集合内元素唯一

  2. ​计算交集与并集​​:

    • ​交集​​:两个集合共有的元素(set1 ∩ set2)。

    • ​并集​​:两个集合所有唯一元素的总和(set1 ∪ set2)。

      并集大小可通过公式优化:并集大小 = len(set1) + len(set2) - 交集大小,避免直接计算并集集合以节省内存

  3. ​相似度计算​​:

    使用公式:

    相似度=100×并集大小/交集大小​

    结果需四舍五入取整(题目样例均为整数)。

  4. ​边界处理​​:

    • 若并集大小为 0(即两集合均为空),相似度为 0(题目保证有解,可不显式处理)

三.代码

老师代码:

#include<iostream>
#include<set>
using namespace std;
int t,n,m,x;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>t;
	while(t--){
		set<int>s;
		cin>>n>>m;
		for(int i=1;i<=n+m;i++){
			cin>>x;
			s.insert(x);
		}
		int a=(n+m-s.size());
		int b=s.size()-a;
		cout<<100*a/(a+b)<<"\n";
	}
	return 0;
}

T5.产生冠军

一.题目要求

有一群人,打乒乓球比赛,两两捉对撕杀,每两个人之间最多打一场比赛。
球赛的规则如下:
如果A打败了B,B又打败了C,而A与C之间没有进行过比赛,那么就认定,A一定能打败C。
如果A打败了B,B又打败了C,而且,C又打败了A,那么A、B、C三者都不可能成为冠军。
根据这个规则,无需循环较量,或许就能确定冠军。你的任务就是面对一群比赛选手,在经过了若干场撕杀之后,确定是否已经实际上产生了冠军。

输入描述

输入含有一些选手群,每群选手都以一个整数n(n<1000)开头,后跟n对选手的比赛结果,比赛结果以一对选手名字(中间隔一空格)表示,前者战胜后者。如果n为0,则表示输入结束。

输出描述

对于每个选手群,若你判断出产生了冠军,则在一行中输出“Yes”,否则在一行中输出“No”。

输入样例
  1. 3
  2. Alice Bob
  3. Smith John
  4. Alice Smith
  5. 5
  6. a c
  7. c d
  8. d e
  9. b e
  10. a d
  11. 0
输出样例
  1. Yes
  2. No
数据范围

对于100%的数据:1≤𝑡≤10,1≤𝑛,𝑚≤1e5

二.解题思路

  1. ​冠军的唯一性条件​​:

    • 冠军必须​​没有输过任何比赛​​(即从未出现在比赛结果的右侧)。

    • 满足条件的冠军候选人​​必须只有一个​​。若存在多个未输过的选手,则无法确定冠军(如环形胜负关系导致多人无败绩)。

  2. ​集合操作法

    • ​所有选手集合(s):记录所有出现在输入中的选手(无论胜败)。

    • ​失败选手集合  (s1):记录所有曾出现在比赛结果右侧(即输过比赛)的选手。

    • ​冠军存在条件​​:当s的大小比s1恰好大1​​ 时,说明存在唯一未失败的选手,即冠军。

三.代码

老师代码:

#include<iostream>
#include<set>
using namespace std;
int n;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	while(cin>>n&&n){
		string x,y;
		set<string>s,s1;
		for(int i=1;i<=n;i++){
			cin>>x>>y;
			s.insert(x);
			s.insert(y);
			s1.insert(y);
		}
		if(s.size()-s1.size()==1){
			cout<<"Yes"<<"\n";
		}
		else{
			cout<<"No"<<"\n";
		}
	}
	return 0;
}

T6.超级会员

一.题目要求

小可超市有三个服务员同时处理订单,但是小可超市为了赚更多钱,推出了十个等级 VIP 服务,所以结账时不能简单按照先来先结帐的原则。级别为10的优先权最高,级别为1的优先权最低。服务员在结账时,则会在他的队伍里面选择一个优先权最高的人进行结账。如果遇到两个优先权一样的顾客的话,则选择最早来排队的顾客。

现在请你帮助小可超市模拟这个结账过程。

输入描述

输入数据包含多组测试,请处理到文件结束。
每组数据第一行有一个正整数 n 表示发生事件的数目。
接下来有 n 行分别表示发生的事件。
一共有两种事件:
1:IN A B,表示有一个拥有优先级B的顾客要求服务员A结账。
2:OUT A,表示服务员A进行了一次结账,结账完毕后,顾客离店。

输出描述

对于每个OUT A事件,请在一行里面输出顾客的编号ID。如果该事件时无顾客需要结账,则输出EMPTY
顾客的编号ID的定义为:在一组测试中,IN A B事件发生第K次时,进来的顾客ID即为K。从1开始编号。

输入样例
  1. 7
  2. IN 1 1
  3. IN 1 2
  4. OUT 1
  5. OUT 2
  6. IN 2 1
  7. OUT 2
  8. OUT 1
  9. 2
  10. IN 1 1
  11. OUT 1
输出样例
  1. 2
  2. EMPTY
  3. 3
  4. 1
  5. 1
数据范围

g对于100%的数据:1≤𝑛≤2000,1≤𝐴≤3,1≤𝐵≤10

二.解题思路

  1. 数据结构设计​

    • ​struct结构体​​:封装顾客属性(ID、VIP等级、服务员ID)

    • ​重载运算符​​:在结构体内定义operator<实现自定义排序规则

  2. ​优先级规则​

    • ​首要规则​​:VIP等级越高越优先(10 > 1)

    • ​次要规则​​:同VIP等级时,ID越小越优先(先到达先服务)

  3. ​队列管理​

    • 为每个服务员(1-3号)创建独立的优先队列

    • 使用priority_queue<int>自动按重载的运算符排序

三.代码

老师代码:

#include<iostream>
#include<queue>
using namespace std;
int n;
struct node{
	int x,id;
	bool operator<(const node &t)const{
		if(x==t.x) return id>t.id;
		return x<t.x;
	}
};
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	while(cin>>n){
		string s;
		int a,b;
		int k=0;
		priority_queue<node> q[5],qq;
		q[1]=q[2]=q[3]=qq;
		for(int i=1;i<=n;i++){
			cin>>s;
			if(s=="IN"){
				cin>>a>>b;
				q[a].push({b,++k});
			}
			else{
				cin>>a;
				if(q[a].empty()) cout<<"EMPTY\n";
				else{
					cout<<q[a].top().id<<"\n";
					q[a].pop();
				}
			}
		}
	}
	return 0;
}

T7.序列最值

一.题目要求

初始时,有一个空的序列,接下来会有 n 次操作。

操作一共有三种,每个操作首先用参数op表示第几种操作,具体表示为:

op=1时,再给定一个参数 x,你要向序列插入 x 这个元素。

op=2 时,删除序列中最早插入的元素,输出被删除的这个元素是第几个被插入到序列的。

op=3时,删除序列中数值最大的元素,输出被删除的这个元素是第几个被插入到序列的,特别的,如果有若干个元素都满足值最大这个要求,要删除的元素是最早被插入的元素。

输入描述

第一行:输入一个正整数 n ,表示操作数量。

接下来 n 行:每行输入一个操作,具体操作形式见题目描述。

输出描述

对于操作2 3,输出对应结果。

数据保证序列为空时不会有操作2 3。

输入样例
  1. 8
  2. 1 8
  3. 1 10
  4. 1 6
  5. 3
  6. 2
  7. 1 9
  8. 2
  9. 3
输出样例
  1. 2 1 3 4
数据范围

对于100%的数据:1≤𝑛≤5∗1e5,1≤𝑥≤5∗1e5,1≤𝑜𝑝≤3

二.解题思路

  1. ​数据结构设计​​:

    • ​普通队列​​:按插入顺序存储元素,用于快速访问最早插入的元素。

    • ​最大堆(优先队列)​​:按自定义规则排序元素(数值大的优先,数值相同则插入序号小的优先),用于快速访问最大值。

    • ​标记数组​​:记录每个元素是否被删除,避免重复操作。

  2. ​操作逻辑​​:

    • ​插入操作(op=1)​​:将元素同时加入普通队列和最大堆,并分配唯一插入序号。

    • ​删除最早元素(op=2)​​:

      • 跳过普通队列中已被标记删除的队首元素。

      • 删除当前队首元素(最早插入的未删除元素),标记并输出其插入序号。

    • ​删除最大值(op=3)​​:

      • 跳过最大堆中已被标记删除的堆顶元素。

      • 删除当前堆顶元素(最大且最早插入的未删除元素),标记并输出其插入序号。

三.代码

老师代码:

#include<iostream>
#include<queue>
#include<map>
using namespace std;
int n,x,y;
struct node1{
	int v,id;
	bool operator<(const node1 &t)const{
		if(v==t.v) return id>t.id;
		return v<t.v;
	}
};
struct node{
	int v,id;
	bool operator<(const node &t)const{
		return id>t.id;
	}
};
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	priority_queue<node>q;
	priority_queue<node1>q1;
	map<int,int>a;
	int k=0;
	while(n--){
		cin>>x;
		if(x==1){
			cin>>y;
			q.push({y,++k});
			q1.push({y,k});
		}
		else if(x==2){
			while(q.empty()!=1&&a[q.top().id]){
				q.pop();
			}
			cout<<q.top().id<<" ";
			a[q.top().id]=1;
			q.pop();
		}
		else if(x==3){
			while(q1.empty()!=1&&a[q1.top().id]){
				q1.pop();
			}
			cout<<q1.top().id<<" ";
			a[q1.top().id]=1;
			q1.pop();
		}
	}
	return 0;
}

T8.最小函数值

一.题目要求

有 𝑛n 个函数,分别为 𝐹1,𝐹2,...,𝐹𝑛​​。定义 𝐹𝑖(𝑥)=𝐴𝑖𝑥2+𝐵𝑖𝑥+𝐶𝑖(𝑥∈𝑁∗)。给定这些 𝐴𝑖、𝐵𝑖​​ 和 𝐶𝑖​​,请求出所有函数的所有函数值中最小的 𝑚 个(如有重复的要输出多个)。

输入描述

第一行输入两个正整数 𝑛 和 𝑚。

以下 𝑛 行每行三个正整数,其中第 𝑖i 行的三个数分别为 𝐴i​、𝐵i 和 𝐶𝑖。

输出描述

输出将这 𝑛n 个函数所有可以生成的函数值排序后的前 𝑚m 个元素。这 𝑚m 个数应该输出到一行,用空格隔开。

输入样例
  1. 3 10
  2. 4 5 3
  3. 3 4 5
  4. 1 7 1
输出样例
  1. 9 12 12 19 25 29 31 44 45 54
数据范围

对于100%的数据:1≤𝑛,𝑚≤10000,1≤𝐴𝑖≤10,1≤𝐵𝑖≤100,1≤𝐶𝑖≤1e4

二.解题思路

  1. ​问题分析​​:

    • 每个二次函数 Fi​(x)在 x≥1时单调递增(因 Ai​>0),最小值出现在 x=1或最小值点附近。

    • 我们需要生成所有函数的最小值序列,并从中选取前 m个最小值。

  2. ​关键观察​​:

    • 最小值可能分布在各个函数的较小 x值处。

    • 使用最小堆动态维护当前各函数的最小候选值,避免全量计算。

  3. ​算法选择​​:

    • ​最小堆(优先队列)​​:存储三元组(函数值、函数索引、当前 x值),按函数值升序排列。

    • ​初始化​​:对每个函数计算 x=1时的值并加入堆。

    • ​迭代生成​​:重复 m次:

      • 弹出堆顶元素(当前最小值),记录到结果。

      • 根据弹出的函数索引,计算其下一个 x的函数值并加入堆。

三.代码

老师代码:

#include<iostream>
#include<queue>
using namespace std;
const int N=1e5+10;
int n,m,a,b,c;
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	priority_queue<long long> q;
	cin>>n>>m;
	while(n--){
		cin>>a>>b>>c;
		for(int x=1;x<=m;x++){
			long long p=a*x*x+b*x+c;
			if(q.size()==m){
				if(p<q.top()){
					q.pop();
				}
				else{
					break;
				}
			}
			q.push(p);
		}
	}
	long long ans[N];
	for(int i=m;i>=1;i--){
		ans[i]=q.top();
		q.pop();
	}
	for(int i=1;i<=m;i++){
		cout<<ans[i]<<" ";
	}
	return 0;
}

T9.玩具序列

一.题目要求

小可是一个只有三岁的小孩,他喜欢玩玩具车。他有 𝑛n 辆玩具车被保存在书架上。

架子上的玩具车 ,小可拿不到,但地板上的他可以拿到。达达会帮小可拿架子上的玩具车到地板上。

地板最多只能放 𝑘 辆玩具车。

当地板已经放了 𝑘 辆玩具车时,达达都会从地板上先拿走一个玩具车放回书架,再拿来小可想要的玩具车。

现在小可一共想依次玩 𝑝个玩具车,问达达最少需要拿几次玩具车。(只算拿下来的,不算拿上去的)

输入描述

第一行三个整数 𝑛,𝑘和 𝑝。

接下来 𝑝p行,每一行有且仅有一个整数 𝑎𝑖​​,表示小可想玩的玩具玩家车编号。

输出描述

输出一行一个整数,表示最少达达最少需要拿玩家车的次数。

输入样例
  1. 3 2 7
  2. 1 2 3 1 3 1 2
输出样例
  1. 4
数据范围

对于100%的数据:1≤𝑘≤𝑛≤1e5,1≤𝑝≤5×1e5,1≤𝑎𝑖≤𝑝

二.解题思路

  1. ​问题建模​​:

    • 书架视为存储池,地板视为容量为 k的缓存。

    • 每次请求玩具车时,若车已在地板上(缓存命中),则无需操作;否则需从书架拿车(缓存未命中),当地板满时需先移出一辆车。

    • ​目标​​:最小化缓存未命中次数(即拿车次数)。

  2. ​最优策略​​:

    • 预处理请求序列,计算每个位置的下一次请求位置(若之后无请求则标记为 INF)。

  3. ​数据结构​​:

    • ​最大堆(优先队列)​​:存储 (下次出现位置, 玩具车)对,堆顶是下次出现位置最远的车(用于置换)。

    • ​惰性删除机制​​:堆中保留过期的记录,弹出时跳过无效记录(车已移出或位置已更新)。

三.代码

老师代码:

#include<iostream>
#include<queue>
#include<map>
using namespace std;
const int N=1e6+10;
int a[N],n,k,p,ans=0,nx[N];
struct node{
	int x,nx;
	bool operator <(const node &t)const{
		return nx<t.nx;
	}
};
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	priority_queue<node> q;
	map<int,int>vis;
	cin>>n>>k>>p;
	for(int i=1;i<=p;i++){
		cin>>a[i];
	}
	for(int i=p;i>=1;i--){
		if(!vis[a[i]]) nx[i]=N;
		else nx[i]=vis[a[i]];
		vis[a[i]]=i;
	}
	vis.clear();
	for(int i=1;i<=p;i++){
		int x=a[i];
		if(!vis[x]){
			if(q.size()>=k){
				vis[q.top().x]=0;
				q.pop();
			}
			q.push({x,nx[i]});
			vis[x]=1;
			ans++;
		}
		else{
			q.push({x,nx[i]});
			k++;
		}
	}
	cout<<ans;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值