洛谷【数据结构1-1】线性表
P3156 【深基15.例1】询问学号
题目描述
有 n(n≤2×1^6) 名同学陆陆续续进入教室。我们知道每名同学的学号(在 1 到 10^9之间),按进教室的顺序给出。上课了,老师想知道第 i 个进入教室的同学的学号是什么(最先进入教室的同学 i=1),询问次数不超过 10^5次。
输入格式
第一行 2 个整数 n 和 m,表示学生个数和询问次数。
第二行 n 个整数,表示按顺序进入教室的学号。
第三行 m 个整数,表示询问第几个进入教室的同学。
输出格式
m 个整数表示答案,用换行隔开。
输入输出样例
10 3 1 9 2 60 8 17 11 4 5 14 1 5 9
#include<iostream> using namespace std; #define N 2000005 int a[N]; int n,m; int main(void) { ios::sync_with_stdio(false); cin>>n>>m; for(int i=1;i<=n;++i) cin>>a[i]; while(m--) cin>>n,cout<<a[n]<<'\n'; return 0; }
P3613 【深基15.例2】寄包柜
题目描述
超市里有 n(n≤105) 个寄包柜。每个寄包柜格子数量不一,第 ii 个寄包柜有 ai(ai≤105) 个格子,不过我们并不知道各个 a_iai 的值。对于每个寄包柜,格子编号从 1 开始,一直到 ai。现在有 q(q≤1^5) 次操作:
1 i j k
:在第 i 个柜子的第 j个格子存入物品 k(0≤k≤1^9)。当 k=0 时说明清空该格子。2 i j
:查询第 i 个柜子的第 j 个格子中的物品是什么,保证查询的柜子有存过东西。
已知超市里共计不会超过 10^7个寄包格子,ai 是确定然而未知的,但是保证一定不小于该柜子存物品请求的格子编号的最大值。当然也有可能某些寄包柜中一个格子都没有。
输入格式
第一行 2 个整数 n 和 q,寄包柜个数和询问次数。
接下来 q 个整数,表示一次操作。
输出格式
对于查询操作时,输出答案。
输入输出样例
输入 #1
5 4
1 3 10000 114514
1 1 1 1
2 3 10000
2 1 1
输出 #1
114514
1
解题思路:我们看到n的范围是1e5,ai的范围是1e5,所以直接开数组空间会炸掉
所以我们可以写个结构体,然后里面用vector表示寄包柜的格子,因为寄包格子不会超过1e7
所以可以开两个vector做,一个表示格子数,另一个存储物品,但是注意每次查询的时候从后向前搜索,找到该格子的位置就break。
否则可能会找到多个答案,但是之前的那些答案对我们并没有什么用,因为更新掉了。
code:
#include <iostream> #include <vector> #define N 100005 using namespace std; struct node { vector<int> num, key; //num存储格子,key存储格子里面的物品 int s = 0; } a[N];//a[i]的每个元素表示一个寄包柜 int n, q; int main(void) { ios::sync_with_stdio(false); cin >> n >> q; int t, i, j, k, p; while (q--) { cin >> p >> i >> j; if (p == 1) { cin >> k; a[i].s++; a[i].num.push_back(j);//更新num的值 a[i].key.push_back(k);//更新key的值 } else { for (int t = a[i].s; t >= 0; --t) { if (a[i].num[t] == j)//当找到j这个格子就跳出,因为前面的元素没有用了。 { cout << a[i].key[t] << '\n'; break; } } } } return 0; }
P1449 后缀表达式
题目描述
所谓后缀表达式是指这样的一个表达式:式中不再引用括号,运算符号放在两个运算对象之后,所有计算按运算符号出现的顺序,严格地由左而右新进行(不用考虑运算符的优先级)。
如:3*(5–2)+7对应的后缀表达式为:3.5.2.-*7.+@。’@’为表达式的结束符号。‘.’为操作数的结束符号。
输入格式
输入:后缀表达式
输出格式
输出:表达式的值
输入输出样例
输入 #1
3.5.2.-*7.+@
输出 #1
16
说明/提示
字符串长度,1000内。
解题思路:看到后缀表达式,首先想到的就是栈,先写一个读数字的函数,遍历表达式
把每次读到的数字放进栈中,如果遇到运算符,那么栈中的元素个数必定大于等于2
所以直接取出栈顶元素,然后pop再和栈顶元素运算,再pop,再把新运算出来的结果放进栈顶
最后直接输出栈顶元素就行(必定只有一个元素,最后的元素可pop,也可不用pop)
code:
#include <cstring> #include <iostream> #include <stack> using namespace std; string str; stack<int> p; int t, times; int get(int i)//获取字符串中数字的函数 { int j = i; int key = str[i] - '0'; for (i++; i < str.length(); ++i)//模拟取值 { if (str[i] >= '0' && str[i] <= '9') { key = key * 10 + str[i] - '0'; } else break; } times = i - j;//主串中要跳过的字符数 return key; } int main(void) { ios::sync_with_stdio(false); int sum = 0; cin >> str; for (int i = 0; i < str.length(); ++i) { if (str[i] >= '0' && str[i] <= '9') { t = get(i); p.push(t);//把结果push进去 i += times;//跳过无用字符 } else if (str[i] == '+') { sum = p.top(); p.pop(); sum += p.top(); p.pop(); p.push(sum); } else if (str[i] == '-') { sum = p.top(); p.pop(); sum = p.top() - sum; p.pop(); p.push(sum); } else if (str[i] == '*') { sum = p.top(); p.pop(); sum *= p.top(); p.pop(); p.push(sum); } else if (str[i] == '/') { sum = p.top(); p.pop(); sum = p.top() / sum; p.pop(); p.push(sum); } else if (str[i] == '@') break; } cout << p.top() << endl;//栈中必定只有一个元素 return 0; }
P1996 约瑟夫问题
题目描述
n 个人围成一圈,从第一个人开始报数,数到 m 的人出列,再由下一个人重新从 11 开始报数,数到 m 的人再出圈,依次类推,直到所有的人都出圈,请输出依次出圈人的编号。
输入格式
输入两个整数 n,m。
输出格式
输出一行 n 个整数,按顺序输出每个出圈人的编号。
输入输出样例
输入 #1
10 3
输出 #1
3 6 9 2 7 1 8 5 10 4
说明/提示
1≤m,n≤100
解题思路:经典队列模拟问题,当然也可用数学方法做(更快),我们直接用队列模拟,如果队首是该出圈的人
就把他输出,然后pop掉(就出圈了,不用再数),否则先把队首元素先push进队尾,再pop掉(类似于重新排队),直到队空退出。
code:
#include <iostream> #include <queue> using namespace std; int main() { int n, m; queue<int> p; cin >> n >> m; for (int i = m; i <= n; ++i)//先把从[m,n]的元素加入队列,第一个肯定第一个输出,
//其实这题数据挺弱的,m其实可能大于n的,但是这个代码过了
//%%%% p.push(i); for (int i = 1; i < m; ++i) p.push(i); int i = 0; while (p.size()) { if (i % m == 0)//当前这个人为出圈的人 cout << p.front() << " ";//输出 else p.push(p.front());//重新排队 p.pop(); i++; } }
P1160 队列安排
题目描述
一个学校里老师要将班上N个同学排成一列,同学被编号为1∼N,他采取如下的方法:
-
先将1号同学安排进队列,这时队列中只有他一个人;
-
2−N号同学依次入列,编号为i的同学入列方式为:老师指定编号为i的同学站在编号为1∼(i−1)中某位同学(即之前已经入列的同学)的左边或右边;
-
从队列中去掉M(M<N)个同学,其他同学位置顺序不变。
在所有同学按照上述方法队列排列完毕后,老师想知道从左到右所有同学的编号。
输入格式
第1行为一个正整数N,表示了有N个同学。
第2-N行,第ii行包含两个整数k,p,其中k为小于ii的正整数,p为0或者1。若p为0,则表示将ii号同学插入到k号同学的左边,p为1则表示插入到右边。
第N+1行为一个正整数M,表示去掉的同学数目。
接下来M行,每行一个正整数x,表示将x号同学从队列中移去,如果x号同学已经不在队列中则忽略这一条指令。
输出格式
1行,包含最多N个空格隔开的正整数,表示了队列从左到右所有同学的编号,行末换行且无空格。
输入输出样例
输入 #1
4
1 0
2 1
1 0
2
3
3
输出 #1
2 4 1
说明/提示
样例解释:
将同学2插入至同学1左边,此时队列为:
2 1
将同学33插入至同学2右边,此时队列为:
2 3 1
将同学4插入至同学1左边,此时队列为:
2 3 4 1
将同学3从队列中移出,此时队列为:
2 4 1
同学3已经不在队列中,忽略最后一条指令
最终队列:
2 4 1
数据范围
对于20%的数据,有N≤10;
对于40%的数据,有N≤1000;
对于100%的数据,有N,M≤100000。
解题思路:可能大多数人想到的就是直接写一个链表模拟,但是这样会T的(看数据就知道,直接用链表做的话可能只能的40%的分?)
我的思路是开三个数组,第一个存储同学是否存在,第二个存储当前同学左边的同学的位置,第三个存储当前同学右边的位置,然后根据指令不断更新后两个数组(类似于链表的插入更新)。
最后单独写一个输出的函数,我们不难发现第0个元素一定是最前面的,(有点像有头节点的的链表,而第0个元素就是这个头节点,那么就好办了)
code:
#include <iostream> using namespace std; #define N 100005 int al[N], ar[N], a[N]; int n, m, k, p, x; void print() { int t = ar[0]; while (ar[t]) { cout << t << " "; t = ar[t]; } cout << t << '\n';//注意这里的格式,好像是要行末输出换行且无空格 } int main(void) { ios::sync_with_stdio(false); cin >> n; al[1] = 0;//初始化 ar[n] = 0;//初始化,在判断边界的时候有用 ar[0] = 1;//初始化 for (int i = 2; i <= n; ++i) { cin >> k >> p; if (p) //将i号同学插入到k的右边 { if (ar[k])//如果右边有元素 { al[ar[k]] = i; ar[i] = ar[k]; al[i] = k; ar[k] = i; } else//如果插入的是最后一个位置(也就是第k个同学的右边什么都没有=_=) { ar[k] = i; al[i] = k; } } else //将i号同学插入到k的左边 { //由于插在左边的时候,不可能插在头节点的左边,所以我们可以直接这样做>_< //这样也能更新头节点右边的值 ar[al[k]] = i; al[i] = al[k]; ar[i] = k; al[k] = i; } // cout<<"--------------------\n"; 这是我调试用的>_<,下面那个也是 // print(); // cout<<"--------------------\n"; } cin >> m; while (m--) { cin >> x; if (a[x])//如果当前同学已经被删除了 continue; else//删除操作 { if (al[x] && !ar[x])//还是考虑最右边的情况 ar[al[x]] = 0; else ar[al[x]] = ar[x], al[ar[x]] = al[x]; a[x] = 1; } // cout<<"--------------------\n"; // print(); // cout<<"--------------------\n"; } print();//输出函数 return 0; }
P1540 机器翻译
题目背景
小晨的电脑上安装了一个机器翻译软件,他经常用这个软件来翻译英语文章。
题目描述
这个翻译软件的原理很简单,它只是从头到尾,依次将每个英文单词用对应的中文含义来替换。对于每个英文单词,软件会先在内存中查找这个单词的中文含义,如果内存中有,软件就会用它进行翻译;如果内存中没有,软件就会在外存中的词典内查找,查出单词的中文含义然后翻译,并将这个单词和译义放入内存,以备后续的查找和翻译。
假设内存中有 M 个单元,每单元能存放一个单词和译义。每当软件将一个新单词存入内存前,如果当前内存中已存入的单词数不超过 M−1,软件会将新单词存入一个未使用的内存单元;若内存中已存入 M 个单词,软件会清空最早进入内存的那个单词,腾出单元来,存放新单词。
假设一篇英语文章的长度为 N 个单词。给定这篇待译文章,翻译软件需要去外存查找多少次词典?假设在翻译开始前,内存中没有任何单词。
输入格式
共 22 行。每行中两个数之间用一个空格隔开。
第一行为两个正整数 M,N,代表内存容量和文章的长度。
第二行为 NN 个非负整数,按照文章的顺序,每个数(大小不超过 1000)代表一个英文单词。文章中两个单词是同一个单词,当且仅当它们对应的非负整数相同。
输出格式
一个整数,为软件需要查词典的次数。
输入输出样例
输入 #1
3 7
1 2 1 5 4 4 1
输出 #1
5
数据范围
对于 10% 的数据有 M=1,N≤5;
对于 100% 的数据有 1≤M≤100,1≤N≤1000。
解题思路:我们可以开一个队列还有一个数组,队列保存当前内存栈中存储的单词,数组表示该单词是否存在内存栈中
然后我们模拟这个过程,一开始内存栈中的元素为0,所有的单词都是需要查询的,不断的读入单词,然后查看数组是否存在
当然,当读入的元素个数大于k的时候就要更新内存栈和p数组。
code:
#include <iostream> #include <map> #include <queue> using namespace std; int p[1005]; int main(void) { ios::sync_with_stdio(false); int m, n, t, k = 0; queue<int> q; cin >> m >> n; int sum = 0;//sum表示的是查询的次数 for (int i = 1; i <= n; ++i) { cin >> t; if (k < m)//当内存栈中存储的数据小于k的时候 { if (p[t] == 0)//如果该单词未被存储 { sum++, p[t] = 1, k++; q.push(t); } } else { if (p[t] == 0) { sum++, p[t] = 1; p[q.front()] = 0;//更新p数组 q.pop();//更新内存栈 q.push(t); } } } cout << sum << endl; return 0; }
P2058 海港
题目描述
小K是一个海港的海关工作人员,每天都有许多船只到达海港,船上通常有很多来自不同国家的乘客。
小K对这些到达海港的船只非常感兴趣,他按照时间记录下了到达海港的每一艘船只情况;对于第i艘到达的船,他记录了这艘船到达的时间ti (单位:秒),船上的乘 客数ki,以及每名乘客的国籍 x_{i,1}, x_{i,2},…,x_{i,k}xi,1,xi,2,…,xi,k。
小K统计了n艘船的信息,希望你帮忙计算出以每一艘船到达时间为止的2424小时(2424小时=86400秒)内所有乘船到达的乘客来自多少个不同的国家。
形式化地讲,你需要计算n条信息。对于输出的第i条信息,你需要统计满足ti−86400<tp≤ti的船只p,在所有的xp,j中,总共有多少个不同的数。
输入格式
第一行输入一个正整数n,表示小K统计了n艘船的信息。
接下来n行,每行描述一艘船的信息:前两个整数ti和ki分别表示这艘船到达海港的时间和船上的乘客数量,接下来ki个整数x_{i,j}xi,j表示船上乘客的国籍。
保证输入的ti是递增的,单位是秒;表示从小K第一次上班开始计时,这艘船在第t_iti秒到达海港。
保证 1≤n≤105,∑ki≤3∗105 ,1≤x(i,j)≤105, 1≤t(i−1)≤ti≤10^9。
其中∑ki表示所有的ki的和。
输出格式
输出n行,第i行输出一个整数表示第i艘船到达后的统计信息。
输入输出样例
输入 #1
3
1 4 4 1 2 2
2 2 2 3
10 1 3
输出 #1
3
4
4
输入 #2
4
1 4 1 2 2 3
3 2 2 3
86401 2 3 4
86402 1 5
输出 #2
3
3
3
4
解题思路:由于ti是递增的,我们可以考虑模拟时间流的方式进行运算,设置三个数组
第一个表示第i个乘客的国籍,第二个表示第第i个国籍的人数,第三表示第i个乘客的到达时间
定义一个sum,表示的是当前国家数目,每次输入x(i,j)的时候,如果x国籍的人数未0,sum就++
然后每次更新的时候把时间差大于24小时的人数略过,然后更新sum的值(如果该国籍数为0,sum--)
具体看代码吧>_<
code:
#include <iostream> using namespace std; #define N 100005 int t[N * 3], X[N], KI[N * 3]; //t数组存放的是第i个乘客的国籍 //X数组存放的是第i个国籍的人数 //KI数组存放的是第i个乘客的到达时间 int sum, n;//sum表示的是当前乘客的国籍总数 int ti, ki, temp, cnt, ct; int main(void) { cnt = ct = sum = 0; ios::sync_with_stdio(false); cin >> n; for (int i = 0; i < n; ++i) { cin >> ti >> ki; for (int j = 1; j <= ki; ++j) { cin >> temp; t[cnt] = temp; KI[cnt++] = ti; if (!X[temp])//如果temp的国籍人数为0,sum自增 sum++; X[temp]++; } while (ti - KI[ct] >= 86400)//要用while循环,因为时间差可能好几个都大于86400 { X[t[ct]]--; if (!X[t[ct]])//如果该乘客的国籍人数为0,国籍总数就-- sum--; ct++; } cout << sum << '\n';//直接输出就行 } return 0; } //关键就是维护三个数组,原理很简单的
P1241 括号序列
题目描述
定义如下规则序列(字符串):
1.空序列是规则序列;
2.如果S是规则序列,那么(S)和[S]也是规则序列;
3.如果A和B都是规则序列,那么AB也是规则序列。
例如,下面的字符串都是规则序列:
(),[],(()),([]),()[],()[()]
而以下几个则不是:
(,[,],)(,()),([()
现在,给你一些由‘(’,‘)’,‘[’,‘]’构成的序列,你要做的,是补全该括号序列,即扫描一遍原序列,对每一个右括号,找到在它左边最靠近它的左括号匹配,如果没有就放弃。在以这种方式把原序列匹配完成后,把剩下的未匹配的括号补全。
输入格式
输入文件仅一行,全部由‘(’,‘)’,‘[’,‘]’组成,没有其他字符,长度不超过100。
输出格式
输出文件也仅有一行,全部由‘(’,‘)’,‘[’,‘]’组成,没有其他字符,把你补全后的规则序列输出即可。
输入输出样例
输出 #1
()[]()
说明/提示
将前两个左括号补全即可。
解题思路:这题真“阅读理解题”,题目的含义我开始看了好几遍都没看懂,好了看重点:对每一个右括号找到最近的左括号
如果不匹配(比如:右括号‘)’,但是找到的左括号是'[')或者没找到就放弃,也就是说,题目的意思是首先满足找到一个左括号
再判断是否匹配,如果匹配的话,在当前位置做个标记,有标记的直接输出,没有标记的就输出一个完整的左右括号。由于题目的数据很小,所以可以直接暴力。
code1:
#include <cstdio> #include <cstring> #include <iostream> #include <stack> int is[105]; char ch[105]; using namespace std; int main(void) { ios::sync_with_stdio(false); cin >> ch; for (int i = 0; i <= strlen(ch); ++i) { if (ch[i] == ']') { for (int j = i - 1; j >= 0; --j) { if (!is[j] && ch[j] == '[')//如果匹配 { is[i] = is[j] = 1;//标记 break;//注意要跳出 } else if (!is[j] && ch[j] == '(')//不匹配 { break;//直接跳出,不用看前面的!!!,这点太坑了T^T } } } else if (ch[i] == ')')//同上 { for (int j = i - 1; j >= 0; --j) { if (!is[j] && ch[j] == '(') { is[i] = is[j] = 1; break; } else if (!is[j] && ch[j] == '[') { break; } } } } for (int i = 0, len = strlen(ch); i < len; ++i) { if (is[i])//如果标记了,那么肯定能组合成一个完整的左右括号 putchar(ch[i]); else { if (ch[i] == '(' || ch[i] == ')')//输出完整的左右括号 printf("()"); else if (ch[i] == '[' || ch[i] == ']') printf("[]"); } } return 0; }
当然上面的是O(n^2)的时间复杂度,我们可以想想优化,我们要搜索左边第一个左括号,其实不用遍历去搜索
我们把每次读到的左括号放进一个栈里面,每次匹配的时候拿出来比较就行。复杂度O(n)
code:
#include <cstdio> #include <cstring> #include <iostream> #include <stack> int is[105]; char ch[105]; using namespace std; int main(void) { ios::sync_with_stdio(false); stack<char> last; stack<int> loc; cin >> ch; int len = strlen(ch); for (int i = 0; i < len; ++i) { if (ch[i] == '(' || ch[i] == '[') last.push(ch[i]), loc.push(i); else { if (ch[i] == ')') { if (last.size()) { if (last.top() == '(') { is[i] = is[loc.top()] = 1; loc.pop(); last.pop(); } } } else if (ch[i] == ']') { if (loc.size()) { if (last.top() == '[') { is[i] = is[loc.top()] = 1; loc.pop(); last.pop(); } } } } } for (int i = 0; i < len; ++i) { if (is[i]) cout << ch[i]; else { if (ch[i] == '(' || ch[i] == ')') cout << "()"; else cout << "[]"; } } return 0; }
P4387 【深基15.习9】验证栈序列
题目描述
给出两个序列 pushed 和 poped 两个序列,其取值从 1 到 n(n≤100000)。已知入栈序列是 pushed,如果出栈序列有可能是 poped,则输出 Yes
,否则输出 No
。为了防止骗分,每个测试点有多组数据。
输入格式
第一行一个整数 q,询问次数。
接下来 q 个询问,对于每个询问:
第一行一个整数 n 表示序列长度;
第二行 n 个整数表示入栈序列;
第二行 n 个整数表示出栈序列;
输出格式
对于每个询问输出答案。
输入输出样例
#include <bits/stdc++.h> using namespace std; #define N 100005 int q, n; int a[N], b[N]; int main(void) { ios::sync_with_stdio(false); cin >> q; while (q--) { stack<int> p; cin >> n; for (int i = 1; i <= n; ++i) cin >> a[i]; for (int i = 1; i <= n; ++i) cin >> b[i]; int i, j; for (i = 1, j = 1; i <= n; ++i) { p.push(a[i]); while (!p.empty() && p.top() == b[j])//判断是否能出栈,最关键的就是这个。。。 { p.pop(); j++; } } if (p.empty()) puts("Yes"); else puts("No"); } }
over