我不想写可恶的补题报告!!!
T1.锯齿矩阵(例题)
一.题目要求
锯齿矩阵是指每一行包含的元素个数不一定相同的矩阵,比如:
3 5 2 6 12 3 41 6 2 72 3 5
初始时矩阵为空,读入𝑚 对整数 (𝑥,𝑦),表示在第 𝑥行的末尾加上一个元素 𝑦。
输出最终的锯齿矩阵。
输入描述
第一行:输入两个正整数n和m,n表示锯齿矩阵的行数,m表示插入次数。
接下来m行:每行输入两个整数x和y,表示在第x行的末尾插入一个元素y。
输出描述
一共输出 n 行,每行若干个用空格分隔的整数。如果某行没有任何元素,则输出一个空行。
输入样例
3 121 32 22 32 43 13 61 51 21 63 23 71 1
输出样例
3 5 2 6 12 3 41 6 2 7
数据范围
对于100%的数据:1≤𝑛,𝑚≤1e5,1≤𝑥≤𝑛,0≤𝑦≤1e5
二.解题思路
-
初始化存储结构:我们需要一个存储每行数据的结构。由于行数固定为n(从1到n),我们可以创建一个长度为n+1的数组(或列表),每个元素是一个动态数组(如C++的vector)来存储该行的元素。
-
处理插入操作:对于每次插入操作(共m次),读取行号x和要添加的值y,然后将y添加到第x行的动态数组末尾,这个操作的时间复杂度是O(1)。
-
输出结果:遍历每一行(从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去重且从小到大排序之后的结果。
输入样例
510 8 7 8 10
输出样例
7810
数据范围
对于100%的数据:1≤𝑛≤1e5,−109≤𝑎𝑖≤1e9
二.解题思路
-
读取输入数据:
-
读取数组元素个数n
-
读取包含n个整数的数组
-
-
高效去重操作:
-
使用集合(set)数据结构自动去重
-
集合的特性:元素唯一,但无序
-
-
排序操作:
-
将去重后的集合转换为列表
-
对列表进行升序排序
-
-
输出结果:
-
遍历排序后的列表
-
每个元素单独一行输出
-
三.代码
老师代码:
#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行:输出一个数字,表示对应询问的答案
输入样例
61 51 21 82 32 93 141 21 32 23 1
输出样例
1246
数据范围
对于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个整数,表示第一个集合内的元素 𝑎𝑖ai。
第三行:输入m个整数,表示第二个集合内的元素 𝑏𝑖bi。
输出描述
对于每一组数据,输出一行一个答案,表示两个集合的相似度。
输入样例
22 31 22 3 43 35 3 43 4 1
输出样例
2550
数据范围
对于100%的数据:1≤𝑡≤10,1≤𝑛,𝑚≤1e5,0≤𝑎𝑖,𝑏𝑖≤2∗1e5
二.解题思路
-
数据预处理:
将输入的列表转换为集合(
。set),自动去除重复元素,确保集合内元素唯一 -
计算交集与并集:
-
交集:两个集合共有的元素(
set1 ∩ set2)。 -
并集:两个集合所有唯一元素的总和(
set1 ∪ set2)。并集大小可通过公式优化:
。并集大小 = len(set1) + len(set2) - 交集大小,避免直接计算并集集合以节省内存
-
-
相似度计算:
使用公式:
相似度=100×并集大小/交集大小
结果需四舍五入取整(题目样例均为整数)。
-
边界处理:
-
若并集大小为 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”。
输入样例
3Alice BobSmith JohnAlice Smith5a cc dd eb ea d0
输出样例
YesNo
数据范围
对于100%的数据:1≤𝑡≤10,1≤𝑛,𝑚≤1e5
二.解题思路
-
冠军的唯一性条件:
-
冠军必须没有输过任何比赛(即从未出现在比赛结果的右侧)。
-
满足条件的冠军候选人必须只有一个。若存在多个未输过的选手,则无法确定冠军(如环形胜负关系导致多人无败绩)。
-
-
集合操作法:
-
所有选手集合(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开始编号。
输入样例
7IN 1 1IN 1 2OUT 1OUT 2IN 2 1OUT 2OUT 12IN 1 1OUT 1
输出样例
2EMPTY311
数据范围
g对于100%的数据:1≤𝑛≤2000,1≤𝐴≤3,1≤𝐵≤10
二.解题思路
-
数据结构设计
-
struct结构体:封装顾客属性(ID、VIP等级、服务员ID)
-
重载运算符:在结构体内定义
operator<实现自定义排序规则
-
-
优先级规则
-
首要规则:VIP等级越高越优先(10 > 1)
-
次要规则:同VIP等级时,ID越小越优先(先到达先服务)
-
-
队列管理
-
为每个服务员(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。
输入样例
81 81 101 6321 923
输出样例
2 1 3 4
数据范围
对于100%的数据:1≤𝑛≤5∗1e5,1≤𝑥≤5∗1e5,1≤𝑜𝑝≤3
二.解题思路
-
数据结构设计:
-
普通队列:按插入顺序存储元素,用于快速访问最早插入的元素。
-
最大堆(优先队列):按自定义规则排序元素(数值大的优先,数值相同则插入序号小的优先),用于快速访问最大值。
-
标记数组:记录每个元素是否被删除,避免重复操作。
-
-
操作逻辑:
-
插入操作(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 个数应该输出到一行,用空格隔开。
输入样例
3 104 5 33 4 51 7 1
输出样例
9 12 12 19 25 29 31 44 45 54
数据范围
对于100%的数据:1≤𝑛,𝑚≤10000,1≤𝐴𝑖≤10,1≤𝐵𝑖≤100,1≤𝐶𝑖≤1e4
二.解题思路
-
问题分析:
-
每个二次函数 Fi(x)在 x≥1时单调递增(因 Ai>0),最小值出现在 x=1或最小值点附近。
-
我们需要生成所有函数的最小值序列,并从中选取前 m个最小值。
-
-
关键观察:
-
最小值可能分布在各个函数的较小 x值处。
-
使用最小堆动态维护当前各函数的最小候选值,避免全量计算。
-
-
算法选择:
-
最小堆(优先队列):存储三元组(函数值、函数索引、当前 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行,每一行有且仅有一个整数 𝑎𝑖,表示小可想玩的玩具玩家车编号。
输出描述
输出一行一个整数,表示最少达达最少需要拿玩家车的次数。
输入样例
3 2 71 2 3 1 3 1 2
输出样例
4
数据范围
对于100%的数据:1≤𝑘≤𝑛≤1e5,1≤𝑝≤5×1e5,1≤𝑎𝑖≤𝑝
二.解题思路
-
问题建模:
-
书架视为存储池,地板视为容量为
k的缓存。 -
每次请求玩具车时,若车已在地板上(缓存命中),则无需操作;否则需从书架拿车(缓存未命中),当地板满时需先移出一辆车。
-
目标:最小化缓存未命中次数(即拿车次数)。
-
-
最优策略:
-
预处理请求序列,计算每个位置的下一次请求位置(若之后无请求则标记为
INF)。
-
-
数据结构:
-
最大堆(优先队列):存储
(下次出现位置, 玩具车)对,堆顶是下次出现位置最远的车(用于置换)。 -
惰性删除机制:堆中保留过期的记录,弹出时跳过无效记录(车已移出或位置已更新)。
-
三.代码
老师代码:
#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;
}
977

被折叠的 条评论
为什么被折叠?



