L2-024 部落 (25 分)
在一个社区里,每个人都有自己的小圈子,还可能同时属于很多不同的朋友圈。我们认为朋友的朋友都算在一个部落里,于是要请你统计一下,在一个给定社区中,到底有多少个互不相交的部落?并且检查任意两个人是否属于同一个部落。
输入格式:
输入在第一行给出一个正整数N(≤104),是已知小圈子的个数。随后N行,每行按下列格式给出一个小圈子里的人:
K P[1] P[2] ⋯ P[K]
其中K是小圈子里的人数,P[i](i=1,⋯,K)是小圈子里每个人的编号。这里所有人的编号从1开始连续编号,最大编号不会超过104。
之后一行给出一个非负整数Q(≤104),是查询次数。随后Q行,每行给出一对被查询的人的编号。
输出格式:
首先在一行中输出这个社区的总人数、以及互不相交的部落的个数。随后对每一次查询,如果他们属于同一个部落,则在一行中输出Y,否则输出N。
输入样例:
4
3 10 1 2
2 3 4
4 1 5 7 8
3 9 6 4
2
10 5
3 7
输出样例:
10 2
Y
N
思路:并查集典型题,将不同小部落的的人之间利用并查集相互unit,利用set结构保存所有人员编号。遍历所有人员,求并查集根节点父节点的个数,即为互不相交的部落的个数。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define maxn 10005
int parents[maxn];
int find(int x) {
return parents[x] == x ? x : parents[x] = find(parents[x]);
}
void unit(vector<int> person) {
int y;
int cnt = 0;
for(int x : person){
if(cnt != 0){
int rootx = find(x);
int rooty = find(y);
if(rootx != rooty) parents[rootx] = rooty;
}
else y = x;
cnt++;
}
}
int main(){
int n;
cin >> n;
for(int i = 0; i < maxn; i++) parents[i] = i;
int k;
vector<int> person;
set<int> pt;
for(int i = 0; i < n; i++){
cin >> k;
int p;
while(k--){
cin >> p;
person.push_back(p);
pt.insert(p);
}
unit(person);
person.clear();
}
int num = 0;
for(int a:pt){
if(find(a) == a) num++;
}
cout << pt.size() << " " << num << endl;
int q;
cin >> q;
int a,b;
while(q--){
cin >> a >> b;
if(find(a) == find(b)) cout << "Y" << endl;
else cout << "N" << endl;
}
return 0;
}
L2-025 分而治之 (25 分)
分而治之,各个击破是兵家常用的策略之一。在战争中,我们希望首先攻下敌方的部分城市,使其剩余的城市变成孤立无援,然后再分头各个击破。为此参谋部提供了若干打击方案。本题就请你编写程序,判断每个方案的可行性。
输入格式:
输入在第一行给出两个正整数 N 和 M(均不超过10 000),分别为敌方城市个数(于是默认城市从 1 到 N 编号)和连接两城市的通路条数。随后 M 行,每行给出一条通路所连接的两个城市的编号,其间以一个空格分隔。在城市信息之后给出参谋部的系列方案,即一个正整数 K (≤ 100)和随后的 K 行方案,每行按以下格式给出:
Np v[1] v[2] ... v[Np]
其中 Np 是该方案中计划攻下的城市数量,后面的系列 v[i] 是计划攻下的城市编号。
输出格式:
对每一套方案,如果可行就输出YES,否则输出NO。
输入样例:
10 11
8 7
6 8
4 5
8 4
8 1
1 2
1 4
9 8
9 1
1 10
2 4
5
4 10 3 8 4
6 6 1 7 5 4 9
3 1 8 4
2 2 8
7 9 8 7 6 5 4 2
输出样例:
NO
YES
YES
NO
NO
思路:使用邻接表存储每个城市的关联城市,标记被摧毁的城市,如果剩下的城市没有一个邻接城市是未被摧毁的,方案可行。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define maxn 10005
int n,m;
vector<int> grid[maxn];
int main(){
cin >> n >> m;
int x,y;
for(int i = 0; i < m; i++){
cin >> x >> y;
grid[x].push_back(y);
grid[y].push_back(x);
}
int k;
cin >> k;
int np,ni;
while(k--){
int citys[maxn] = {0};
bool flag = true;
cin >> np;
while(np--){
cin >> ni;
citys[ni] = 1;
}
for(int i = 1; i <= n; i++){
if(citys[i]) continue;
for(const int a : grid[i]){
if(!citys[a]) flag = false;
}
}
if(flag) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
L2-026 小字辈
本题给定一个庞大家族的家谱,要请你给出最小一辈的名单。
输入格式:
输入在第一行给出家族人口总数 N(不超过 100 000 的正整数) —— 简单起见,我们把家族成员从 1 到 N 编号。随后第二行给出 N 个编号,其中第 i 个编号对应第 i 位成员的父/母。家谱中辈分最高的老祖宗对应的父/母编号为 -1。一行中的数字间以空格分隔。
输出格式:
首先输出最小的辈分(老祖宗的辈分为 1,以下逐级递增)。然后在第二行按递增顺序输出辈分最小的成员的编号。编号间以一个空格分隔,行首尾不得有多余空格。
输入样例:
9
2 6 5 5 -1 5 6 4 7
输出样例:
4
1 9
思路:利用一个数组,数组中每一为代表一个人,记录每个人的后代。随后利用队列,多源bfs搜索,每次将每一辈的成员均弹出,并将后一辈全部弹入。每一次大循环更新一次辈分。
AC代码如下:
#include<bits/stdc++.h>
using namespace std;
#define maxn 100005
vector<int> child[maxn];
int main(){
int n;
int p;
cin >> n;
for(int i = 1; i <= n; i++){
cin >> p;
if(p == -1) child[0].push_back(i);
else child[p].push_back(i);
}
queue<int> q;
q.push(0);
int ans = 0;
int c;
set<int> min_c;
while(!q.empty()){
min_c.clear();
int size = q.size();
for(int i = 0; i < size; i++){
c = q.front();
min_c.insert(c);
q.pop();
for(const int t: child[c]) {
if(t != -1) q.push(t);
}
}
ans++;
}
cout << ans-1 << endl;
vector<int> mm_c (min_c.begin(),min_c.end());
for(int i = 0; i < mm_c.size(); i++){
if(i == mm_c.size() -1) cout << mm_c[i] << endl;
else cout << mm_c[i] << " ";
}
return 0;
}
L2-027 名人堂与代金券 (25 分)
对于在中国大学MOOC(http://www.icourse163.org/ )学习“数据结构”课程的学生,想要获得一张合格证书,总评成绩必须达到 60 分及以上,并且有另加福利:总评分在 [G, 100] 区间内者,可以得到 50 元 PAT 代金券;在 [60, G) 区间内者,可以得到 20 元PAT代金券。全国考点通用,一年有效。同时任课老师还会把总评成绩前 K 名的学生列入课程“名人堂”。本题就请你编写程序,帮助老师列出名人堂的学生,并统计一共发出了面值多少元的 PAT 代金券。
输入格式:
输入在第一行给出 3 个整数,分别是 N(不超过 10 000 的正整数,为学生总数)、G(在 (60,100) 区间内的整数,为题面中描述的代金券等级分界线)、K(不超过 100 且不超过 N 的正整数,为进入名人堂的最低名次)。接下来 N 行,每行给出一位学生的账号(长度不超过15位、不带空格的字符串)和总评成绩(区间 [0, 100] 内的整数),其间以空格分隔。题目保证没有重复的账号。
输出格式:
首先在一行中输出发出的 PAT 代金券的总面值。然后按总评成绩非升序输出进入名人堂的学生的名次、账号和成绩,其间以 1 个空格分隔。需要注意的是:成绩相同的学生享有并列的排名,排名并列时,按账号的字母序升序输出。
输入样例:
10 80 5
cy@zju.edu.cn 78
cy@pat-edu.com 87
1001@qq.com 65
uh-oh@163.com 96
test@126.com 39
anyone@qq.com 87
zoe@mit.edu 80
jack@ucla.edu 88
bob@cmu.edu 80
ken@163.com 70
输出样例:
360
1 uh-oh@163.com 96
2 jack@ucla.edu 88
3 anyone@qq.com 87
3 cy@pat-edu.com 87
5 bob@cmu.edu 80
5 zoe@mit.edu 80
思路:整道题难度不大,但是细节很多,首先构建一个结构体,保存每个人的序号,名字和分数,随后利用c++内置的sort函数进行排序。排序完后,从大到小遍历,给每个人赋予一个名次,分数与前者相同,名次那么也,而正常的grade自己走自己的,i走一步他走一步。排序完后将序号小于等于k的人的数据输出即可。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define maxn 10005
struct St{
int d;
string name;
int score;
}person[maxn];
bool cmp(St a, St b) {
if (a.score == b.score){
return a.name < b.name;
}else return a.score > b.score;
}
int main(){
int n,g,k;
cin >> n >> g >> k;
int cnt_20 = 0,cnt_50 = 0;
for(int i = 0; i < n; i++){
cin >> person[i].name >> person[i].score;
if(person[i].score >= g) cnt_50++;
else if(person[i].score < g && person[i].score >= 60) cnt_20++;
}
cout << cnt_50*50 + cnt_20*20 << endl;
sort(person,person + n,cmp);
int grade = 1;
St pre;
for(int i = 0;i < n; i++){
if(i == 0){
person[i].d = grade;
}
else{
if(person[i].score == pre.score) person[i].d = pre.d;
else person[i].d = grade;
}
grade++;
pre = person[i];
}
int i = 0;
St now = person[i];
while(now.d <= k && i < n){
cout << now.d << " " << now.name << " " << now.score << endl;
now = person[++i];
}
return 0;
}
L2-028 秀恩爱分得快 (25 分)
古人云:秀恩爱,分得快。
互联网上每天都有大量人发布大量照片,我们通过分析这些照片,可以分析人与人之间的亲密度。如果一张照片上出现了 K 个人,这些人两两间的亲密度就被定义为 1/K。任意两个人如果同时出现在若干张照片里,他们之间的亲密度就是所有这些同框照片对应的亲密度之和。下面给定一批照片,请你分析一对给定的情侣,看看他们分别有没有亲密度更高的异性朋友?
输入格式:
输入在第一行给出 2 个正整数:N(不超过1000,为总人数——简单起见,我们把所有人从 0 到 N-1 编号。为了区分性别,我们用编号前的负号表示女性)和 M(不超过1000,为照片总数)。随后 M 行,每行给出一张照片的信息,格式如下:
K P[1] ... P[K]
其中 K(≤ 500)是该照片中出现的人数,P[1] ~ P[K] 就是这些人的编号。最后一行给出一对异性情侣的编号 A 和 B。同行数字以空格分隔。题目保证每个人只有一个性别,并且不会在同一张照片里出现多次。
输出格式:
首先输出 A PA,其中 PA 是与 A 最亲密的异性。如果 PA 不唯一,则按他们编号的绝对值递增输出;然后类似地输出 B PB。但如果 A 和 B 正是彼此亲密度最高的一对,则只输出他们的编号,无论是否还有其他人并列。
输入样例 1:
10 4
4 -1 2 -3 4
4 2 -3 -5 -6
3 2 4 -5
3 -6 0 2
-3 2
输出样例 1:
-3 2
2 -5
2 -6
输入样例 2:
4 4
4 -1 2 -3 0
2 0 -3
2 2 -3
2 -1 2
-3 2
输出样例 2:
-3 2
思路分析:首先利用二维矩阵存储异性之间的亲密度,注意,为了加速,我们只需要存储与a,b待求异性相关的照片的亲密度即可。同时得到亲密度后,判断a,b之间是否为最大亲密度,如果都为最大亲密度,直接输出。如果其中有任何一个把另一个当做备胎,那就先输出a的最大亲密度的所有其他人,再输出b的。
坑点分析:
1.二维数组赋值只挑选与a,b有关的
2.-0是女性,所以输入应该采用字符串输入才能明确划分性别
3.a,b可能相互亲密度都为0,但他们依然是最亲密的异性。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define maxn 1005
#define maxm 1005
int flag[maxn] = {0};
double dear[maxn][maxn] = {0};
bool ans[maxn] = {0};
vector<int> photos[maxm];
void take_dear(vector<int> v){
int k = v.size();
for(int i = 0; i < k;i++){
for(int j = 0; j < k; j++){
if(flag[v[i]] != flag[v[j]] && v[i] != v[j]){
dear[v[i]][v[j]] += 1.0/k;
}
}
}
}
void Print(int a,int b){
if(flag[a] == 1) cout << "-" << a << " " << b << endl;
else cout << a << " " << "-" << b << endl;
}
int main(){
int n,m;
cin >> n >> m;
int k;
string c;
int id;
for(int i = 0; i < m; i++){
cin >> k;
vector<int> v;
for(int j = 0; j < k; j++){
cin >> c;
id = abs(stoi(c));
if(c[0] == '-'){
flag[id] = 1;
}else flag[id] = 0;
v.push_back(id);
}
photos[i] = v;
}
string sa,sb;
cin >> sa >> sb;
int a = stoi(sa),b = stoi(sb);
a = abs(a),b = abs(b);
if(sa[0] == '-') flag[a] = 1;
for(int i = 0; i < m; i++){
if(count(photos[i].begin(),photos[i].end(),a) == 0 && count(photos[i].begin(),photos[i].end(),b) == 0) continue;
else take_dear(photos[i]);
}
double maxa = dear[a][b];
double maxb = dear[b][a];
bool ismaxa = true;
bool ismaxb = true;
for(int i = 0; i < n; i++){
if(dear[a][i] > maxa) {
maxa = dear[a][i];
ismaxa = false;
}
}
for(int i = 0; i < n; i++){
if(dear[b][i] > maxb) {
maxb = dear[b][i];
ismaxb = false;
}
}
if(ismaxa && ismaxb){
Print(a,b);
}else{
for(int i = 0; i < n; i++){
if(dear[a][i] == maxa && flag[i] != flag[a]){
Print(a,i);
}
}
for(int i = 0; i < n; i++){
if(dear[b][i] == maxb && flag[i] != flag[b]){
Print(b,i);
}
}
}
return 0;
}
L2-029 特立独行的幸福 (25 分)
对一个十进制数的各位数字做一次平方和,称作一次迭代。如果一个十进制数能通过若干次迭代得到 1,就称该数为幸福数。1 是一个幸福数。此外,例如 19 经过 1 次迭代得到 82,2 次迭代后得到 68,3 次迭代后得到 100,最后得到 1。则 19 就是幸福数。显然,在一个幸福数迭代到 1 的过程中经过的数字都是幸福数,它们的幸福是依附于初始数字的。例如 82、68、100 的幸福是依附于 19 的。而一个特立独行的幸福数,是在一个有限的区间内不依附于任何其它数字的;其独立性就是依附于它的的幸福数的个数。如果这个数还是个素数,则其独立性加倍。例如 19 在区间[1, 100] 内就是一个特立独行的幸福数,其独立性为 2×4=8。
另一方面,如果一个大于1的数字经过数次迭代后进入了死循环,那这个数就不幸福。例如 29 迭代得到 85、89、145、42、20、4、16、37、58、89、…… 可见 89 到 58 形成了死循环,所以 29 就不幸福。
本题就要求你编写程序,列出给定区间内的所有特立独行的幸福数和它的独立性。
输入格式:
输入在第一行给出闭区间的两个端点:1<A<B≤104。
输出格式:
按递增顺序列出给定闭区间 [A,B] 内的所有特立独行的幸福数和它的独立性。每对数字占一行,数字间以 1 个空格分隔。
如果区间内没有幸福数,则在一行中输出 SAD。
输入样例 1:
10 40
输出样例 1:
19 8
23 6
28 3
31 4
32 3
注意:样例中,10、13 也都是幸福数,但它们分别依附于其他数字(如 23、31 等等),所以不输出。其它数字虽然其实也依附于其它幸福数,但因为那些数字不在给定区间 [10, 40] 内,所以它们在给定区间内是特立独行的幸福数。
输入样例 2:
110 120
输出样例 2:
SAD
思路:定义flag数组记录数值是否为素数,定义isprime数组记录数值是否为幸运数,定义rely数组记录数值是否独立,也就是在其他数的求解过程中是否出现过该数。随后遍历A,B区间,这里需要注意的是,遇到循环的本质就是中间数值出现了,多次,使用set保存中间出现过的数值,随后判断即可。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define maxn 10005
struct Luck{
int i;
int cnt = 0;
};
struct cmp{
bool operator () (const struct Luck &a,const struct Luck &b){
return a.i < b.i;//降序排序
}
};
bool flag[maxn] = {0};
bool isprime[maxn] = {0};
bool rely[maxn] = {0};
set<Luck,cmp> lucky_num;
void is_luck(int x){
int r = x;
Luck lm;
lm.i = x;
set<int> viewed;
while(x != 1){
int ans = 0;
while(x != 0){
ans += (int) pow((x % 10),2);
x /= 10;
}
if(ans != 1) rely[ans] = true;
x = ans;
lm.cnt++;
if(find(viewed.begin(),viewed.end(),x) != viewed.end()) return;
viewed.insert(ans);
}
lucky_num.insert(lm);
isprime[r] = true;
for(int i = 2; i <= (int)sqrt(r)+1; i++){
if(r % i == 0){
isprime[r] = false;
break;
}
}
}
int main(){
int a, b;
cin >> a >> b;
for(int i = a; i <= b; i++){
is_luck(i);
}
if(lucky_num.size() == 0) cout << "SAD" << endl;
else{
for(Luck a : lucky_num){
if(!rely[a.i])
cout << a.i << " " << a.cnt * (isprime[a.i] + 1) << endl;
}
}
}
L2-030 冰岛人 (25 分)
2018年世界杯,冰岛队因1:1平了强大的阿根廷队而一战成名。好事者发现冰岛人的名字后面似乎都有个“松”(son),于是有网友科普如下:
冰岛人沿用的是维京人古老的父系姓制,孩子的姓等于父亲的名加后缀,如果是儿子就加 sson,女儿则加 sdottir。因为冰岛人口较少,为避免近亲繁衍,本地人交往前先用个 App 查一下两人祖宗若干代有无联系。本题就请你实现这个 App 的功能。
输入格式:
输入首先在第一行给出一个正整数 N(1<N≤105),为当地人口数。随后 N 行,每行给出一个人名,格式为:名 姓(带性别后缀),两个字符串均由不超过 20 个小写的英文字母组成。维京人后裔是可以通过姓的后缀判断其性别的,其他人则是在姓的后面加 m 表示男性、f 表示女性。题目保证给出的每个维京家族的起源人都是男性。
随后一行给出正整数 M,为查询数量。随后 M 行,每行给出一对人名,格式为:名1 姓1 名2 姓2。注意:这里的姓是不带后缀的。四个字符串均由不超过 20 个小写的英文字母组成。
题目保证不存在两个人是同名的。
输出格式:
对每一个查询,根据结果在一行内显示以下信息:
若两人为异性,且五代以内无公共祖先,则输出 Yes;
若两人为异性,但五代以内(不包括第五代)有公共祖先,则输出 No;
若两人为同性,则输出 Whatever;
若有一人不在名单内,则输出 NA。
所谓“五代以内无公共祖先”是指两人的公共祖先(如果存在的话)必须比任何一方的曾祖父辈分高。
输入样例:
15
chris smithm
adam smithm
bob adamsson
jack chrissson
bill chrissson
mike jacksson
steve billsson
tim mikesson
april mikesdottir
eric stevesson
tracy timsdottir
james ericsson
patrick jacksson
robin patricksson
will robinsson
6
tracy tim james eric
will robin tracy tim
april mike steve bill
bob adam eric steve
tracy tim tracy tim
x man april mikes
输出样例:
Yes
No
No
Whatever
Whatever
NA
思路:使用map存储每个人名字、性别以及他的父亲,只有维京人的父辈可推,因此在输入阶段,遇到其他类型的人,不记录父节点,但是需要记录性别(坑点)。随后在每一次查询中,性别相同的直接输出;有任何一个没有找到的直接输出;性别不同的,需要利用一个双重循环,遍历最多25种情况(5*5)。判断是否有相同五代以内父辈,随后输出。
AC代码:
#include <bits/stdc++.h>
using namespace std;
struct Peoson {
char sex;
string father;
};
map<string, Peoson> people;
int judge(string a, string b) {
int i = 1, j;
for (string A = a; !A.empty(); A = people[A].father, i++) {
j = 1;
for (string B = b; !B.empty(); B = people[B].father, j++) {
if (i >= 5 && j >= 5) break;//双方都超出5代之后,不需要继续寻找(测试点6 运行超时)
if (A == B && (i < 5 || j < 5))//五代内出现共同祖先,返回false(测试点3、6答案错误)
return 0;
}
}
return 1;
}
int main() {
int n, m;
string str, a, b;
cin.sync_with_stdio(false);
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a >> b;
if (b.back() == 'n') //儿子
people[a] = { 'm',b.substr(0,b.size() - 4) };
else if (b.back() == 'r') //女儿
people[a] = { 'f',b.substr(0, b.size() - 7) };
else people[a].sex = b.back(); //其他人
}
cin >> m;
for (int i = 0; i < m; i++) {
cin >> a >> str >> b >> str; //姓氏没有用
if (people.find(a) == people.end() || people.find(b) == people.end()) printf("NA\n");
else if (people[a].sex == people[b].sex) printf("Whatever\n");
else printf("%s\n", judge(a, b) ? "Yes" : "No");
}
return 0;
}
L2-031 深入虎穴 (25 分)
著名的王牌间谍 007 需要执行一次任务,获取敌方的机密情报。已知情报藏在一个地下迷宫里,迷宫只有一个入口,里面有很多条通路,每条路通向一扇门。每一扇门背后或者是一个房间,或者又有很多条路,同样是每条路通向一扇门…… 他的手里有一张表格,是其他间谍帮他收集到的情报,他们记下了每扇门的编号,以及这扇门背后的每一条通路所到达的门的编号。007 发现不存在两条路通向同一扇门。
内线告诉他,情报就藏在迷宫的最深处。但是这个迷宫太大了,他需要你的帮助 —— 请编程帮他找出距离入口最远的那扇门。
输入格式:
输入首先在一行中给出正整数 N(< 105),是门的数量。最后 N 行,第 i 行(1 ≤ i ≤ N)按以下格式描述编号为 i 的那扇门背后能通向的门:
K D[1] D[2] ... D[K]
其中 K 是通道的数量,其后是每扇门的编号。
输出格式:
在一行中输出距离入口最远的那扇门的编号。题目保证这样的结果是唯一的。
输入样例:
13
3 2 3 4
2 5 6
1 7
1 8
1 9
0
2 11 10
1 13
0
0
1 12
0
0
输出样例:
12
思路:使用set保存每一个门的出口下一个门的编号,记录所有的有出度的门,利用flag记录,这里有一个坑点就是起点不一定为1,需要找入度为0的结点。随后利用简单的多源广度优先搜索就可以了,也可以采用dfs等手段。用广度优先的好处是,最后一个出去的元素就是最后的结点编号。
AC代码:
#include <bits/stdc++.h>
using namespace std;
#define maxn 100005
set<int> doors[maxn];
bool flag[maxn] = {false};
int main() {
int n;
cin >> n;
int k,t;
for(int i = 1; i <= n; i++) {
cin >> k;
while(k--) {
cin >> t;
doors[i].insert(t);
flag[t] = true;
}
}
queue<int> q;
//寻找起点
int w;
for(int i = 1; i <= n; i++){
if(flag[i] == false) {
w = i;
break;
}
}
q.push(w);
while(!q.empty()) {
int size = q.size();
for(int i = 0; i < size; i++) {
w = q.front();
q.pop();
for(int a : doors[w]){
q.push(a);
}
}
}
cout << w << endl;
return 0;
}
L2-032 彩虹瓶 (25 分)
彩虹瓶的制作过程(并不)是这样的:先把一大批空瓶铺放在装填场地上,然后按照一定的顺序将每种颜色的小球均匀撒到这批瓶子里。
假设彩虹瓶里要按顺序装 N 种颜色的小球(不妨将顺序就编号为 1 到 N)。现在工厂里有每种颜色的小球各一箱,工人需要一箱一箱地将小球从工厂里搬到装填场地。如果搬来的这箱小球正好是可以装填的颜色,就直接拆箱装填;如果不是,就把箱子先码放在一个临时货架上,码放的方法就是一箱一箱堆上去。当一种颜色装填完以后,先看看货架顶端的一箱是不是下一个要装填的颜色,如果是就取下来装填,否则去工厂里再搬一箱过来。
如果工厂里发货的顺序比较好,工人就可以顺利地完成装填。例如要按顺序装填 7 种颜色,工厂按照 7、6、1、3、2、5、4 这个顺序发货,则工人先拿到 7、6 两种不能装填的颜色,将其按照 7 在下、6 在上的顺序堆在货架上;拿到 1 时可以直接装填;拿到 3 时又得临时码放在 6 号颜色箱上;拿到 2 时可以直接装填;随后从货架顶取下 3 进行装填;然后拿到 5,临时码放到 6 上面;最后取了 4 号颜色直接装填;剩下的工作就是顺序从货架上取下 5、6、7 依次装填。
但如果工厂按照 3、1、5、4、2、6、7 这个顺序发货,工人就必须要愤怒地折腾货架了,因为装填完 2 号颜色以后,不把货架上的多个箱子搬下来就拿不到 3 号箱,就不可能顺利完成任务。
另外,货架的容量有限,如果要堆积的货物超过容量,工人也没办法顺利完成任务。例如工厂按照 7、6、5、4、3、2、1 这个顺序发货,如果货架够高,能码放 6 只箱子,那还是可以顺利完工的;但如果货架只能码放 5 只箱子,工人就又要愤怒了……
本题就请你判断一下,工厂的发货顺序能否让工人顺利完成任务。
输入格式:
输入首先在第一行给出 3 个正整数,分别是彩虹瓶的颜色数量 N(1< N≤ 103)、临时货架的容量 M(< N)、以及需要判断的发货顺序的数量 K。
随后 K 行,每行给出 N 个数字,是 1 到N 的一个排列,对应工厂的发货顺序。
一行中的数字都以空格分隔。
输出格式:
对每个发货顺序,如果工人可以愉快完工,就在一行中输出 YES;否则输出 NO。
输入样例:
7 5 3
7 6 1 3 2 5 4
3 1 5 4 2 6 7
7 6 5 4 3 2 1
输出样例:
YES
NO
NO
思路:使用stack栈进行模拟即可,注意一个细节就是在取栈顶元素后再放新元素时,要使用while循环,因为栈顶元素被取了后,栈顶元素可能变为目前所需的,需要把栈顶元素==目标元素掏空后才能新添加元素。由于数据量不大,这里可以采用python,更为方便。
AC代码
n,m,k = map(int,input().split())
for i in range(k):
stack = []
conter = list(map(int,input().split()))
target = 1;
flag = False
for c in conter:
if c == target:
target += 1
elif (len(stack) != 0 and stack[-1] == target):
while len(stack) > 0 and target == stack[-1]:
stack.pop()
target += 1
stack.append(c)
else:
stack.append(c)
if len(stack) > m:
flag = True
break
if len(stack) > m or flag:
print("NO")
else:
while len(stack) > 0 and target == stack[-1]:
stack.pop()
target += 1
if len(stack) == 0:
print("YES")
else:
print("NO")
L2-033 简单计算器 (25 分)
本题要求你为初学数据结构的小伙伴设计一款简单的利用堆栈执行的计算器。如上图所示,计算器由两个堆栈组成,一个堆栈 S1存放数字,另一个堆栈 S2存放运算符。计算器的最下方有一个等号键,每次按下这个键,计算器就执行以下操作:
从 S1中弹出两个数字,顺序为 n1和 n2;
从 S2中弹出一个运算符 op;
执行计算 n2 op n1;
将得到的结果压回 S1。
直到两个堆栈都为空时,计算结束,最后的结果将显示在屏幕上。
输入格式:
输入首先在第一行给出正整数 N(1<N≤103),为 S1中数字的个数。
第二行给出 N 个绝对值不超过 100 的整数;第三行给出 N−1 个运算符 —— 这里仅考虑 +、-、*、/ 这四种运算。一行中的数字和符号都以空格分隔。
输出格式:
将输入的数字和运算符按给定顺序分别压入堆栈 S1和 S2,将执行计算的最后结果输出。注意所有的计算都只取结果的整数部分。题目保证计算的中间和最后结果的绝对值都不超过 109。
如果执行除法时出现分母为零的非法操作,则在一行中输出:ERROR: X/0,其中 X 是当时的分子。然后结束程序。
输入样例 1:
5
40 5 8 3 2
/ * - +
输出样例 1:
2
输入样例 2:
5
2 5 8 4 4
* / - +
输出样例 2:
ERROR: 5/0
思路:使用两个栈模拟即可,注意一个细节就是,所有中间变量都是int类型。
AC代码:
n = int(input())
stack1 = list(map(int,input().strip().split()))
stack2 = input().strip().split()
flag = True
while len(stack1) != 1:
t1 = stack1.pop()
t2 = stack1.pop()
c = stack2.pop()
if c == '+':
stack1.append(t1+t2)
elif c == '-':
stack1.append(t2-t1)
elif c == '*':
stack1.append(t1*t2)
else:
if t1 == 0:
flag = False
print(f"ERROR: %d/0"%t2)
break
else:
stack1.append(t2//t1)
if flag:
print("%d"%(stack1[-1] // 1))
L2-034 口罩发放 (25 分)
为了抗击来势汹汹的 COVID19 新型冠状病毒,全国各地均启动了各项措施控制疫情发展,其中一个重要的环节是口罩的发放。
某市出于给市民发放口罩的需要,推出了一款小程序让市民填写信息,方便工作的开展。小程序收集了各种信息,包括市民的姓名、身份证、身体情况、提交时间等,但因为数据量太大,需要根据一定规则进行筛选和处理,请你编写程序,按照给定规则输出口罩的寄送名单。
输入格式:
输入第一行是两个正整数 D 和 P(1≤D,P≤30),表示有 D 天的数据,市民两次获得口罩的时间至少需要间隔 P 天。
接下来 D 块数据,每块给出一天的申请信息。第 i 块数据(i=1,⋯,D)的第一行是两个整数 Ti 和 Si(1≤Ti,Si≤1000),表示在第 i 天有 Ti条申请,总共有 Si个口罩发放名额。随后 Ti行,每行给出一条申请信息,格式如下:
姓名 身份证号 身体情况 提交时间
给定数据约束如下:
姓名 是一个长度不超过 10 的不包含空格的非空字符串;
身份证号 是一个长度不超过 20 的非空字符串;
身体情况 是 0 或者 1,0 表示自觉良好,1 表示有相关症状;
提交时间 是 hh:mm,为24小时时间(由 00:00 到 23:59。例如 09:08。)。注意,给定的记录的提交时间不一定有序;
身份证号 各不相同,同一个身份证号被认为是同一个人,数据保证同一个身份证号姓名是相同的。
能发放口罩的记录要求如下:
身份证号 必须是 18 位的数字(可以包含前导0);
同一个身份证号若在第 i 天申请成功,则接下来的 P 天不能再次申请。也就是说,若第 i 天申请成功,则等到第 i+P+1 天才能再次申请;
在上面两条都符合的情况下,按照提交时间的先后顺序发放,直至全部记录处理完毕或 Si个名额用完。如果提交时间相同,则按照在列表中出现的先后顺序决定。
输出格式:
对于每一天的申请记录,每行输出一位得到口罩的人的姓名及身份证号,用一个空格隔开。顺序按照发放顺序确定。
在输出完发放记录后,你还需要输出有合法记录的、身体状况为 1 的申请人的姓名及身份证号,用空格隔开。顺序按照申请记录中出现的顺序确定,同一个人只需要输出一次。
输入样例:
4 2
5 3
A 123456789012345670 1 13:58
B 123456789012345671 0 13:58
C 12345678901234567 0 13:22
D 123456789012345672 0 03:24
C 123456789012345673 0 13:59
4 3
A 123456789012345670 1 13:58
E 123456789012345674 0 13:59
C 123456789012345673 0 13:59
F F 0 14:00
1 3
E 123456789012345674 1 13:58
1 1
A 123456789012345670 0 14:11
输出样例:
D 123456789012345672
A 123456789012345670
B 123456789012345671
E 123456789012345674
C 123456789012345673
A 123456789012345670
A 123456789012345670
E 123456789012345674
样例解释:
输出中,第一行到第三行是第一天的部分;第四、五行是第二天的部分;第三天没有符合要求的市民;第六行是第四天的部分。最后两行按照出现顺序输出了可能存在身体不适的人员。
思路:利用结构体存储每个人的情况,注意对于每个人,需要额外存储他还有多久能拿到口罩以及是否在有相关症状中访问过他。注意为了排序方便,表达初始先后顺序,最好对每个人存储列表次序。随后就是一个模拟的过程。
AC代码如下:
#include<bits/stdc++.h>
#include<algorithm>
#define ll long long
#define pii pair<int,int>
#define inf 1e9
using namespace std;
const int N = 110;
const int mod = 1e9+7;
int n, m;
int day_t;
struct node{
string name, sfz;
int body;
int ih, im;
char c;
int turn ;
};
vector<node> a;
map<string,int> p;//记录从哪天开始可以拿到口罩
map<string,int> vis;
bool cmp(node x, node y){
if(x.ih!=y.ih)return x.ih<y.ih;
else if(x.im!=y.im)return x.im<y.im;
return x.turn < y.turn;
}
bool check(string s){
for(int i = 0;i < s.size();i++){
if(!(s[i]>='0'&&s[i]<='9'))return false;
}
return true;
}
void solve(){
day_t++;
int t, s;
cin >> t >> s;
vector<node>v;
for(int i = 0;i < t;i++){
node x;
cin >> x.name >> x.sfz >> x.body >> x.ih >> x.c >> x.im;
x.turn = i;
if(x.sfz.size() == 18 && check(x.sfz)){
if(vis[x.sfz] == 0 && x.body == 1) a.push_back(x), vis[x.sfz] = 1;
v.push_back(x);
}
}
sort(v.begin(),v.end(),cmp);
int cnt = 0;
for(auto i : v){
if(cnt >= s) break;
if(day_t >= p[i.sfz]){
p[i.sfz] = day_t+m+1;
cout << i.name << " " << i.sfz<<"\n";
cnt++;
}
}
}
int main(){
day_t = 0;
cin >> n >> m;
while(n--)
solve();
for(auto i:a){
cout << i.name << " " << i.sfz<<"\n";
}
return 0;
}
L2-035 完全二叉树的层序遍历 (25 分)
一个二叉树,如果每一个层的结点数都达到最大值,则这个二叉树就是完美二叉树。对于深度为 D 的,有 N 个结点的二叉树,若其结点对应于相同深度完美二叉树的层序遍历的前 N 个结点,这样的树就是完全二叉树。
给定一棵完全二叉树的后序遍历,请你给出这棵树的层序遍历结果。
输入格式:
输入在第一行中给出正整数 N(≤30),即树中结点个数。第二行给出后序遍历序列,为 N 个不超过 100 的正整数。同一行中所有数字都以空格分隔。
输出格式:
在一行中输出该树的层序遍历序列。所有数字都以 1 个空格分隔,行首尾不得有多余空格。
输入样例:
8
91 71 2 34 10 15 55 18
输出样例:
18 34 55 71 2 10 15 91
思路:本质上就是后序遍历的重建,注意一点就是给的序列不一定是完整的完全满二叉树,同时注意递归过程中的临界点。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define maxn 35
bool flag[maxn] = {false};
int ans[maxn];
int n;
vector<int> nodes;
int place = 0;
void dfs(int x){
if(x < n){
dfs(2*x+1);
dfs(2*x+2);
ans[x] = nodes[place++];
}
}
int main(){
cin >> n;
int t;
for(int i = 0; i < n; i++){
cin >> t;
nodes.push_back(t);
}
dfs(0);
for(int i = 0; i < n; i++){
if(i != n-1) cout << ans[i] << " ";
else cout << ans[i] << endl;
}
return 0;
}
L2-036 网红点打卡攻略 (25 分)
一个旅游景点,如果被带火了的话,就被称为“网红点”。大家来网红点游玩,俗称“打卡”。在各个网红点打卡的快(省)乐(钱)方法称为“攻略”。你的任务就是从一大堆攻略中,找出那个能在每个网红点打卡仅一次、并且路上花费最少的攻略。
输入格式:
首先第一行给出两个正整数:网红点的个数 N(1 < N ≤ 200)和网红点之间通路的条数 M。随后 M 行,每行给出有通路的两个网红点、以及这条路上的旅行花费(为正整数),格式为“网红点1 网红点2 费用”,其中网红点从 1 到 N 编号;同时也给出你家到某些网红点的花费,格式相同,其中你家的编号固定为 0。
再下一行给出一个正整数 K,是待检验的攻略的数量。随后 K 行,每行给出一条待检攻略,格式为:
n V~1~ V~2~ ⋯ V~n~
其中 n(≤200) 是攻略中的网红点数,Vi是路径上的网红点编号。这里假设你从家里出发,从 V1开始打卡,最后从 Vn回家。
输出格式:
在第一行输出满足要求的攻略的个数。
在第二行中,首先输出那个能在每个网红点打卡仅一次、并且路上花费最少的攻略的序号(从 1 开始),然后输出这个攻略的总路费,其间以一个空格分隔。如果这样的攻略不唯一,则输出序号最小的那个。
题目保证至少存在一个有效攻略,并且总路费不超过 109。
输入样例:
6 13
0 5 2
6 2 2
6 0 1
3 4 2
1 5 2
2 5 1
3 1 1
4 1 2
1 6 1
6 3 2
1 2 1
4 5 3
2 0 2
7
6 5 1 4 3 6 2
6 5 2 1 6 3 4
8 6 2 1 6 3 4 5 2
3 2 1 5
6 6 1 3 4 5 2
7 6 2 1 3 4 5 2
6 5 2 1 4 3 6
输出样例:
3
5 11
样例说明:
第 2、3、4、6 条都不满足攻略的基本要求,即不能做到从家里出发,在每个网红点打卡仅一次,且能回到家里。所以满足条件的攻略有 3 条。
第 1 条攻略的总路费是:(0->5) 2 + (5->1) 2 + (1->4) 2 + (4->3) 2 + (3->6) 2 + (6->2) 2 + (2->0) 2 = 14;
第 5 条攻略的总路费同理可算得:1 + 1 + 1 + 2 + 3 + 1 + 2 = 11,是一条更省钱的攻略;
第 7 条攻略的总路费同理可算得:2 + 1 + 1 + 2 + 2 + 2 + 1 = 11,与第 5 条花费相同,但序号较大,所以不输出。
思路:使用一个结构体Path,储存路径、路径长度以及序号,利用邻接矩阵存储网红路径图。随后对于每一条候选路径,如果路径长度不为n,或者中途有断路,或者中途有重复点,那么该条路径不可行。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define maxn 205
struct Path{
vector<int> p;
int dist = 0;
int id;
};
int grid[maxn][maxn] = {0};
vector<Path> path;
bool cmp(Path a,Path b){
if(a.dist != b.dist) return a.dist < b.dist;
else return a.id < b.id;
}
int main(){
int n,m;
cin >> n >> m;
int t1,t2,c;
for (int i = 0;i < m;i++){
cin >> t1 >> t2 >> c;
grid[t1][t2] = c;
grid[t2][t1] = c;
}
int k;
cin >> k;
string str;
int v,s;
for(int i = 0;i < k;i++){
bool visited[n+1] = {false};
visited[0] = true;
bool flag = false;
int pre = 0;
Path c;
c.id = i;
cin >> v;
if(v != n){
getline(cin,str);
flag = true;
continue;
}
while(v--){
cin >> s;
if(grid[pre][s] != 0 && !visited[s]){
c.p.push_back(s);
c.dist += grid[pre][s];
pre = s;
visited[s] = true;
}
else{
getline(cin,str);
flag = true;
break;
}
}
if(flag || grid[pre][0] == 0) continue;
c.dist += grid[pre][0];
path.push_back(c);
}
sort(path.begin(),path.end(),cmp);
cout << path.size() << endl;
cout << path[0].id + 1 << " " << path[0].dist << endl;
return 0;
}
L2-037 包装机 (25 分)
一种自动包装机的结构如图 1 所示。首先机器中有 N 条轨道,放置了一些物品。轨道下面有一个筐。当某条轨道的按钮被按下时,活塞向左推动,将轨道尽头的一件物品推落筐中。当 0 号按钮被按下时,机械手将抓取筐顶部的一件物品,放到流水线上。图 2 显示了顺序按下按钮 3、2、3、0、1、2、0 后包装机的状态。
一种特殊情况是,因为筐的容量是有限的,当筐已经满了,但仍然有某条轨道的按钮被按下时,系统应强制启动 0 号键,先从筐里抓出一件物品,再将对应轨道的物品推落。此外,如果轨道已经空了,再按对应的按钮不会发生任何事;同样的,如果筐是空的,按 0 号按钮也不会发生任何事。
现给定一系列按钮操作,请你依次列出流水线上的物品。
输入格式:
输入第一行给出 3 个正整数 N(≤100)、M(≤1000)和 Smax(≤100),分别为轨道的条数(于是轨道从 1 到 N 编号)、每条轨道初始放置的物品数量、以及筐的最大容量。随后 N 行,每行给出 M 个英文大写字母,表示每条轨道的初始物品摆放。
最后一行给出一系列数字,顺序对应被按下的按钮编号,直到 −1 标志输入结束,这个数字不要处理。数字间以空格分隔。题目保证至少会取出一件物品放在流水线上。
输出格式:
在一行中顺序输出流水线上的物品,不得有任何空格。
输入样例:
3 4 4
GPLT
PATA
OMSA
3 2 3 0 1 2 0 2 2 0 -1
输出样例:
MATA
思路:使用队列结构和栈结构模拟,同时注意一点就是当传送带上为空,此时虽然栈满,但是也不会弹出东西。
AC代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 105;
queue<char> v[maxn]; ///存储每个轨道上的物品
stack<char> s; ///筐
queue<char> q; ///结果输出
int main(){
int N,M,S;
int a[1005];
char ch;
cin >> N >> M >> S;
getchar();
for(int i = 1;i<= N;i++){
for(int j = 0;j < M;j++){
cin >> ch;
v[i].push(ch);
a[i]++;
}
getchar();
}
int x;
while(true){
cin >> x;
if(x == -1) break;
if(x == 0){
if(s.size() == 0) continue; //如果筐里面的为空就不做任何处理,跳过
else{
q.push(s.top()); //否则,弹出栈顶元素,放入流水线上
s.pop();
}
}
if(x >= 1 && x <= N){
if(v[x].size() == 0) continue; //如果对应轨道上没有物品了不做任何处理,跳过
else{
if(s.size() == S){ //如果筐已经满了,就弹出筐顶物品到流水线上,再将物品放入框中
q.push(s.top());
s.pop();
}
s.push(v[x].front());
v[x].pop();
}
}
}
while(!q.empty()){
cout << q.front();
q.pop();
}
return 0;
}
L2-038 病毒溯源 (25 分)
病毒容易发生变异。某种病毒可以通过突变产生若干变异的毒株,而这些变异的病毒又可能被诱发突变产生第二代变异,如此继续不断变化。
现给定一些病毒之间的变异关系,要求你找出其中最长的一条变异链。
在此假设给出的变异都是由突变引起的,不考虑复杂的基因重组变异问题 —— 即每一种病毒都是由唯一的一种病毒突变而来,并且不存在循环变异的情况。
输入格式
输入在第一行中给出一个正整数 N( ≤ 104),即病毒种类的总数。于是我们将所有病毒从 0 到 N−1 进行编号。
随后 N 行,每行按以下格式描述一种病毒的变异情况:
k 变异株1 …… 变异株k
其中 k 是该病毒产生的变异毒株的种类数,后面跟着每种变异株的编号。第 i 行对应编号为 i 的病毒(0 ≤ i < N)。题目保证病毒源头有且仅有一个。
输出格式
首先输出从源头开始最长变异链的长度。
在第二行中输出从源头开始最长的一条变异链,编号间以 1 个空格分隔,行首尾不得有多余空格。如果最长链不唯一,则输出最小序列。
注:我们称序列 { a1,⋯,an} 比序列 { b1,⋯,bn} “小”,如果存在 1≤k≤n 满足 ai=bi对所有 i < k 成立,且 ak < bk。
输入样例
10
3 6 4 8
0
0
0
2 5 9
0
1 7
1 2
0
2 3 1
1
2
3
4
5
6
7
8
9
10
11
输出样例
4
0 4 9 1
思路:使用深度优先,利用用回溯的思想,记录最长且序列最小的变异链。同时也可以使用多源广度优先。注意一个vector的细节在于vector可以直接进行大小比较,因此能够直接操作进行比较序列大小。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define MAX 10005
bool a[MAX][MAX];
bool flag[MAX];
vector<int> ans, tmp;
int maxn;
int n;
void dfs(int root, int len){
if(len > maxn){
maxn = len;
ans = tmp;
}else if(len == maxn && tmp < ans){
ans = tmp;
}
for(int i = 0; i < n; i++){
if(a[root][i]){
tmp.push_back(i);
dfs(i, len+1);
tmp.pop_back();
}
}
return;
}
int main(){
cin >> n;
for(int i = 0; i < n; i++){
int k;
cin >> k;
for(int j = 0; j < k; j++){
int x;
cin >> x;
flag[x] = true;
a[i][x] = true;
}
}
int root = 0;
while(flag[root]) root++;
tmp.push_back(root);
dfs(root, 1);
cout << ans.size() << endl;
for(int i=0; i < ans.size(); i++){
if(i)
cout << " ";
cout << ans[i];
}
cout << endl;
return 0;
}
L2-039 清点代码库 (25 分)
上图转自新浪微博:“阿里代码库有几亿行代码,但其中有很多功能重复的代码,比如单单快排就被重写了几百遍。请设计一个程序,能够将代码库中所有功能重复的代码找出。各位大佬有啥想法,我当时就懵了,然后就挂了。。。”
这里我们把问题简化一下:首先假设两个功能模块如果接受同样的输入,总是给出同样的输出,则它们就是功能重复的;其次我们把每个模块的输出都简化为一个整数(在 int 范围内)。于是我们可以设计一系列输入,检查所有功能模块的对应输出,从而查出功能重复的代码。你的任务就是设计并实现这个简化问题的解决方案。
输入格式:
输入在第一行中给出 2 个正整数,依次为 N(≤104)和 M(≤102),对应功能模块的个数和系列测试输入的个数。
随后 N 行,每行给出一个功能模块的 M 个对应输出,数字间以空格分隔。
输出格式:
首先在第一行输出不同功能的个数 K。随后 K 行,每行给出具有这个功能的模块的个数,以及这个功能的对应输出。数字间以 1 个空格分隔,行首尾不得有多余空格。输出首先按模块个数非递增顺序,如果有并列,则按输出序列的递增序给出。
注:所谓数列 {A1, …… , AM} 比 {B1, …… , BM} 大,是指存在 1 ≤ i < M,使得A1 = B1, …… , Ai = Bi 成立 , 且 Ai+1 > Bi+1。
输入样例:
7 3
35 28 74
-1 -1 22
28 74 35
-1 -1 22
11 66 0
35 28 74
35 28 74
1
2
3
4
5
6
7
8
输出样例:
4
3 35 28 74
2 -1 -1 22
1 11 66 0
1 28 74 35
思路:使用map结构记录不同类型的输出个数,随后将不同类型的输出保存到数组中,利用数量和序列大小排序。
AC代码:
#include<bits/stdc++.h>
using namespace std;
#define maxn 205
const int N=100003;
struct node{
int cnt;
vector<int>a;
bool operator <(const node &x)const{
if(cnt!=x.cnt) return cnt>x.cnt;
for(int i=0;i<a.size();i++)
if(a[i]!=x.a[i]) return a[i]<x.a[i];
}
}p[N];
map<vector<int>,int>mp;
int main(){
int n,m;
cin>>n>>m;
int tt=0;
for(int i=1;i<=n;i++){
vector<int>t;
for(int j=1;j<=m;j++){
int x;
scanf("%d",&x);
t.push_back(x);
}
if(mp[t])//如果这个vector出现过就直接把出现次数+1即可
p[mp[t]].cnt++;
else{//如果这个vector没出现过就新建一个结构体
mp[t]=++tt;//记录t所对应的结构体编号
p[mp[t]].cnt++;
p[mp[t]].a=t;//把vector赋给结构体
}
}
sort(p+1,p+tt+1);
printf("%d\n",tt);
for(int i=1;i<=tt;i++){
printf("%d",p[i].cnt);
for(int j=0;j<p[i].a.size();j++)
printf(" %d",p[i].a[j]);
if(i != tt) puts("");
}
return 0;
}
L2-040 哲哲打游戏 (25 分)
哲哲是一位硬核游戏玩家。最近一款名叫《达诺达诺》的新游戏刚刚上市,哲哲自然要快速攻略游戏,守护硬核游戏玩家的一切!
为简化模型,我们不妨假设游戏有 N 个剧情点,通过游戏里不同的操作或选择可以从某个剧情点去往另外一个剧情点。此外,游戏还设置了一些存档,在某个剧情点可以将玩家的游戏进度保存在一个档位上,读取存档后可以回到剧情点,重新进行操作或者选择,到达不同的剧情点。
为了追踪硬核游戏玩家哲哲的攻略进度,你打算写一个程序来完成这个工作。假设你已经知道了游戏的全部剧情点和流程,以及哲哲的游戏操作,请你输出哲哲的游戏进度。
输入格式:
输入第一行是两个正整数 N 和 M (1≤N,M≤105),表示总共有 N 个剧情点,哲哲有 M 个游戏操作。
接下来的 N 行,每行对应一个剧情点的发展设定。第 i 行的第一个数字是 Ki,表示剧情点 i 通过一些操作或选择能去往下面 Ki个剧情点;接下来有 Ki个数字,第 k 个数字表示做第 k 个操作或选择可以去往的剧情点编号。
最后有 M 行,每行第一个数字是 0、1 或 2,分别表示:
0 表示哲哲做出了某个操作或选择,后面紧接着一个数字 j,表示哲哲在当前剧情点做出了第 j 个选择。我们保证哲哲的选择永远是合法的。
1 表示哲哲进行了一次存档,后面紧接着是一个数字 j,表示存档放在了第 j 个档位上。
2 表示哲哲进行了一次读取存档的操作,后面紧接着是一个数字 j,表示读取了放在第 j 个位置的存档。
约定:所有操作或选择以及剧情点编号都从 1 号开始。存档的档位不超过 100 个,编号也从 1 开始。游戏默认从 1 号剧情点开始。总的选项数(即 ∑Ki)不超过 106。
输出格式:
对于每个 1(即存档)操作,在一行中输出存档的剧情点编号。
最后一行输出哲哲最后到达的剧情点编号。
输入样例:
10 11
3 2 3 4
1 6
3 4 7 5
1 3
1 9
2 3 5
3 1 8 5
1 9
2 8 10
0
1 1
0 3
0 1
1 2
0 2
0 2
2 2
0 3
0 1
1 1
0 2
输出样例:
1
3
9
10
样例解释:
简单给出样例中经过的剧情点顺序:
1 -> 4 -> 3 -> 7 -> 8 -> 3 -> 5 -> 9 -> 10。
档位 1 开始存的是 1 号剧情点;档位 2 存的是 3 号剧情点;档位 1 后来又存了 9 号剧情点。
思路:首先利用邻接表将所有剧情发展设定保存下来。随后对于每一个游戏操作,分类进行操作即可。
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 100004;
vector<int> v[N];
int dan[N],p = 1;
int main(){
int n,m;
cin >> n >> m;
for(int i = 1;i <= n;i++){
int k;
cin >> k;
while(k--){
int x;
cin >> x;
v[i].push_back(x);
}
}
while(m--){
int x,y;
cin >> x >> y;
if(x == 0){
p = v[p][y-1];
}else if(x == 1){
dan[y] = p;
cout << p << endl;
}else {
p = dan[y];
}
}
cout << p;
return 0;
}