天梯赛补题
L2-039 清点代码库 (25 分)

上图转自新浪微博:“阿里代码库有几亿行代码,但其中有很多功能重复的代码,比如单单快排就被重写了几百遍。请设计一个程序,能够将代码库中所有功能重复的代码找出。各位大佬有啥想法,我当时就懵了,然后就挂了。。。”
这里我们把问题简化一下:首先假设两个功能模块如果接受同样的输入,总是给出同样的输出,则它们就是功能重复的;其次我们把每个模块的输出都简化为一个整数(在 int 范围内)。于是我们可以设计一系列输入,检查所有功能模块的对应输出,从而查出功能重复的代码。你的任务就是设计并实现这个简化问题的解决方案。
输入格式:
输入在第一行中给出 2 个正整数,依次为 N(≤10
4
)和 M(≤10
2
),对应功能模块的个数和系列测试输入的个数。
随后 N 行,每行给出一个功能模块的 M 个对应输出,数字间以空格分隔。
输出格式:
首先在第一行输出不同功能的个数 K。随后 K 行,每行给出具有这个功能的模块的个数,以及这个功能的对应输出。数字间以 1 个空格分隔,行首尾不得有多余空格。输出首先按模块个数非递增顺序,如果有并列,则按输出序列的递增序给出。
注:所谓数列 { A
1
, ..., A
M
} 比 { B
1
, ..., B
M
} 大,是指存在 1≤i<M,使得 A
1
=B
1
,...,A
i
=B
i
成立,且 A
i+1
>B
i+1
。
输入样例:
7 3
35 28 74
-1 -1 22
28 74 35
-1 -1 22
11 66 0
35 28 74
35 28 74
输出样例:
4
3 35 28 74
2 -1 -1 22
1 11 66 0
1 28 74 35
题解:本体思路较为简单,操作比较复杂,综合运用了结构体,set函数,map函数,vector数组,sort函数。比较考验基本功。另外用字符串可能也行,用空尝试一下。
代码:
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll nl=1e4+5;
vector<int>a[nl];
vector<int>b[nl];
#define speed_up ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);//这个题会卡cin是我没想到的
struct node{
int num=0;
int sum=0;
vector<int>ff;//结构体中可以套数组,反正你想涛用的感觉都可以。
}s[nl];
set<vector<int> >se;//用set函数去重
map<vector<int>,int>ma;//记录模板出现的数量
bool cmp(node x,node y){//排序
if(x.sum==y.sum){
return x.ff<y.ff;//这里一个模板直接比较也是看了别人的代码,第一次知道。
}else{
return x.sum>y.sum;
}
}
int main(){
speed_up;
int n,m;
cin>>n>>m;
int i,j;
for(i=0;i<n;i++){
for(j=0;j<m;j++){
ll x;
cin>>x;
a[i].push_back(x);
}
ma[a[i]]++;
se.insert(a[i]);
}
int cnt=se.size();
cout<<cnt<<endl;
set<vector<int> >::iterator it;
cnt=0;
for(it=se.begin();it!=se.end();it++){
s[cnt].num=cnt;
s[cnt].sum=ma[*it];
s[cnt].ff=*it;
cnt++;
}
sort(s,s+cnt,cmp);
for(i=0;i<cnt;i++){
cout<<s[i].sum;
for(j=0;j<s[i].ff.size();j++){
cout<<" "<<s[i].ff[j];
}
cout<<endl;
}
}
```
L2-040 哲哲打游戏 (25 分)
哲哲是一位硬核游戏玩家。最近一款名叫《达诺达诺》的新游戏刚刚上市,哲哲自然要快速攻略游戏,守护硬核游戏玩家的一切!
为简化模型,我们不妨假设游戏有 N 个剧情点,通过游戏里不同的操作或选择可以从某个剧情点去往另外一个剧情点。此外,游戏还设置了一些存档,在某个剧情点可以将玩家的游戏进度保存在一个档位上,读取存档后可以回到剧情点,重新进行操作或者选择,到达不同的剧情点。
为了追踪硬核游戏玩家哲哲的攻略进度,你打算写一个程序来完成这个工作。假设你已经知道了游戏的全部剧情点和流程,以及哲哲的游戏操作,请你输出哲哲的游戏进度。
输入格式:
输入第一行是两个正整数 N 和 M (1≤N,M≤10
5
),表示总共有 N 个剧情点,哲哲有 M 个游戏操作。
接下来的 N 行,每行对应一个剧情点的发展设定。第 i 行的第一个数字是 K
i
,表示剧情点 i 通过一些操作或选择能去往下面 K
i
个剧情点;接下来有 K
i
个数字,第 k 个数字表示做第 k 个操作或选择可以去往的剧情点编号。
最后有 M 行,每行第一个数字是 0、1 或 2,分别表示:
0 表示哲哲做出了某个操作或选择,后面紧接着一个数字 j,表示哲哲在当前剧情点做出了第 j 个选择。我们保证哲哲的选择永远是合法的。
1 表示哲哲进行了一次存档,后面紧接着是一个数字 j,表示存档放在了第 j 个档位上。
2 表示哲哲进行了一次读取存档的操作,后面紧接着是一个数字 j,表示读取了放在第 j 个位置的存档。
约定:所有操作或选择以及剧情点编号都从 1 号开始。存档的档位不超过 100 个,编号也从 1 开始。游戏默认从 1 号剧情点开始。总的选项数(即 ∑K
i
)不超过 10
6
。
输出格式:
对于每个 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 号剧情点。
题解:这个题非常简单,就是题目长度和所给样例限制了我的想象。。。很轻松就可以18分,比赛时这就可以了。所以下次注意。题目要求输出存档点,和最后到达的剧情点。
18分代码:(短短几行18分很赚)
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll nl=1e4+5;//1e5会编译错误,数组太大
ll a[nl][nl]={0};//定义一个二维数组,记录不同关卡可以分别到达的剧情点
ll b[nl];
int main(){
ll n,m;
cin>>n>>m;
ll i,j;
for(i=1;i<=n;i++){
ll k;
cin>>k;
for(j=0;j<k;j++){
cin>>a[i][j];
}
}
ll num=1;//起始剧情点
while(m--){
ll x,y;
cin>>x>>y;
if(x==1){
cout<<num<<endl;
b[y]=num;//b[y]用于记录存档点
}else if(x==0){
num=a[num][y-1];//记录剧情点转移
}else{
num=b[y];//读取存档
}
}
cout<<num;//输出最后剧情点
}
```
25分代码:
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll nl=1e5+5;
vector<vector<int>> v(100005);//定义成vector数组,具体为啥这样就过了还是问师哥去
ll b[nl]={0};
int main(){
ll n,m;
cin>>n>>m;
ll i,j;
for(i=1;i<=n;i++){
ll k;
cin>>k;
for(j=0;j<k;j++){
ll x;
cin>>x;
v[i].push_back(x);
}
}
ll num=1;
for(i=0;i<m;i++){
ll x,y;
cin>>x>>y;
if(x==1){
cout<<num<<endl;
b[y]=num;
}else if(x==2){
num=b[y];
}else{
num=v[num][y-1];
}
}
cout<<num<<endl;
}
```
L2-038 病毒溯源 (25 分)

病毒容易发生变异。某种病毒可以通过突变产生若干变异的毒株,而这些变异的病毒又可能被诱发突变产生第二代变异,如此继续不断变化。
现给定一些病毒之间的变异关系,要求你找出其中最长的一条变异链。
在此假设给出的变异都是由突变引起的,不考虑复杂的基因重组变异问题 —— 即每一种病毒都是由唯一的一种病毒突变而来,并且不存在循环变异的情况。
输入格式:
输入在第一行中给出一个正整数 N(≤10
4
),即病毒种类的总数。于是我们将所有病毒从 0 到 N−1 进行编号。
随后 N 行,每行按以下格式描述一种病毒的变异情况:
k 变异株1 …… 变异株k
其中 k 是该病毒产生的变异毒株的种类数,后面跟着每种变异株的编号。第 i 行对应编号为 i 的病毒(0≤i<N)。题目保证病毒源头有且仅有一个。
输出格式:
首先输出从源头开始最长变异链的长度。
在第二行中输出从源头开始最长的一条变异链,编号间以 1 个空格分隔,行首尾不得有多余空格。如果最长链不唯一,则输出最小序列。
注:我们称序列 { a
1
,⋯,a
n
} 比序列 { b
1
,⋯,b
n
} “小”,如果存在 1≤k≤n 满足 a
i
=b
i
对所有 i<k 成立,且 a
k
<b
k
。
输入样例:
10
3 6 4 8
0
0
0
2 5 9
0
1 7
1 2
0
2 3 1
输出样例:
4
0 4 9 1
本题较难,一看就是用并查集或dfs。题意:病毒代代相传,源头只有一个(祖先就一个),求最长链。当时比赛时感觉会用dfs,但时间问题感觉写不出来,就用了并查集,得到20分(没有加入同长度链的比较)。后来用并查集加入了同长度链比较,结果只得了16分。。。多了一个内存超限错误。下面上代码:
20分:
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll nl=1e5+5;
ll p[nl];
ll b[nl]={0};
ll c[nl];
vector<ll>v[nl];
void init(){
for(ll i=0;i<nl;i++){
p[i]=i;
}
}
ll num=1;
ll find(ll x){
while(x!=p[x]){
num++;
x=p[x];
}
return num;
}
int main(){
init();
ll n;
cin>>n;
ll i,j;
for(i=0;i<n;i++){
ll k;
cin>>k;
while(k--){
ll x;
cin>>x;
p[x]=i;
b[x]=1;
}
}
ll a=0;
ll b;
for(i=0;i<n;i++){
num=1;//初始化num,不然find一下就改变了
if(a<find(i)){
num=1;//同理,不可缺少
a=find(i);
b=i;
}
}
cout<<a<<endl;
//cout<<b<<endl;
i=0;
while(b!=p[b]){//这里并没有进行同链长的排序
c[i]=b;
b=p[b];
i++;
}
c[i]=b;//别忘记记录b==p[b]时的值。
//cout<<i<<endl;
cout<<c[i];
for(j=i-1;j>=0;j--){
cout<<" "<<c[j];
}
}
```
16分代码:多了内存超限的错误
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll nl=1e5+5;
ll p[nl];
ll b[nl]={0};
ll c[nl];
vector<ll>v[nl];
void init(){
for(ll i=0;i<nl;i++){
p[i]=i;
}
}
ll num=1;
ll i,j;
ll find(ll x){
while(x!=p[x]){
v[i].push_back(x);
num++;
x=p[x];
}
v[i].push_back(x);
return num;
}
bool cmp(vector<ll>a,vector<ll>b){//进行了同链长的排序
if(a.size()!=b.size()){
return a.size()>b.size();
}else if(a!=b){
return a<b;
}
}
int main(){
init();
ll n;
cin>>n;
//ll i,j;
for(i=0;i<n;i++){
ll k;
cin>>k;
while(k--){
ll x;
cin>>x;
p[x]=i;
b[x]=1;
}
}
ll a=0;
ll b;
for(i=0;i<n;i++){
//v[i].push_back(i);
//cout<<find(i)<<endl;
num=1;
if(a<find(i)){
num=1;
a=find(i);
b=i;
}
}
cout<<a<<endl;
sort(v,v+n,cmp);
cout<<v[0][a-1];
for(i=a-2;i>=0;i--){
cout<<" "<<v[0][i];
}
}
```
25分代码:dfs代码
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll nl=1e4+5;
ll a[nl]={0};//用于查找源点
ll b[nl][nl]={0};//记录两病毒之间是否有亲属关系
vector<ll>v,ve;//用于记录最长链,vector的好用之处,它可以直接对序列进行比较***
ll n,maxn;//maxn即是最长距离
void dfs(ll x,ll y){
if(y>maxn){
maxn=y;
ve=v;
}else if(y==maxn){
if(v<ve){//真的好用,省去很多代码
ve=v;
}
}
for(ll i=0;i<n;i++){
if(b[x][i]==1){
v.push_back(i);//插入一个新的点
dfs(i,y+1);//让新的序列去递归
v.pop_back(); //在删去此点,因为病毒可以衍生多个不同的新病毒为其他点腾地方
}
}
}
int main(){
cin>>n;
ll i,j;
for(i=0;i<n;i++){
ll k;
cin>>k;
while(k--){
ll x;
cin>>x;
a[x]=1;
b[i][x]=1;
}
}
ll u=0;
for(i=0;i<n;i++){
if(a[i]==0){
u=i;//找到源点
break;
}
}
v.push_back(u);//初始化v
dfs(u,1);
cout<<ve.size()<<endl;
for(i=0;i<ve.size();i++){
if(i==0){
cout<<ve[0];
}else{
cout<<" "<<ve[i];
}
}
}
```
L1-080 乘法口诀数列 (20 分)
本题要求你从任意给定的两个 1 位数字 a
1
和 a
2
开始,用乘法口诀生成一个数列 {a
n
},规则为从 a
1
开始顺次进行,每次将当前数字与后面一个数字相乘,将结果贴在数列末尾。如果结果不是 1 位数,则其每一位都应成为数列的一项。
输入格式:
输入在一行中给出 3 个整数,依次为 a
1
、a
2
和 n,满足 0≤a
1
,a
2
≤9,0<n≤10
3
。
输出格式:
在一行中输出数列的前 n 项。数字间以 1 个空格分隔,行首尾不得有多余空格。
输入样例:
2 3 10
输出样例:
2 3 6 1 8 6 8 4 8 4
样例解释:
数列前 2 项为 2 和 3。从 2 开始,因为 2×3=6,所以第 3 项是 6。因为 3×6=18,所以第 4、5 项分别是 1、8。依次类推…… 最后因为第 6 项有 6×8=48,对应第 10、11 项应该是 4、8。而因为只要求输出前 10 项,所以在输出 4 后结束。
题解:此题当时考虑复杂了,原因是基本功不扎实,对于vector数组补熟悉。题意很简单,就是坑很多。。。
上代码:
```cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll nl=2;
vector<int>v;
int a[nl];
int main(){
int n,m;
cin>>n>>m;
ll i,j;
ll k;
cin>>k;
ll num=2;
v.push_back(n);
v.push_back(m);
int x;
int y;
i=0;
j=1;
while(1){
x=v[i]*v[j];
i++;
j++;
y=0;
while(x!=0){
a[y]=x%10;
x/=10;
y++;
}
if(y==0){//这个题很容易发生段错误,原因就是计算过程中x的值可能为零,导致y为零,这会导致后面的代码无法运行,v数组的长度不发生改变,程序陷入死循环。所以此时向v数组中插入零即可。
v.push_back(0);
}
for(ll z=y-1;z>=0;z--){
v.push_back(a[z]);
}
if(v.size()>=k){
break;
}
}
for(i=0;i<k;i++){
if(i==0){
cout<<v[i];
}else{
cout<<" "<<v[i];
}
}
}
```
浙公网安备 33010602011771号