2021团体天梯赛题解

L1-01 人与神 (5 分)

分析:
无脑题,直接干

#include
using namespace std;
int main(){
    cout<<"To iterate is human, to recurse divine.";
    return 0;
}

L1-02 两小时学完C语言 (5 分)

分析:
简单减法题。

#include
using namespace std;
int main(){
    int n,k,m;
    cin>>n>>k>>m;
    cout<<n-k*m;
    return 0;
}

L1-03强迫症 (10 分)

分析:
输入的时候用字符串输入,然后分别分析字符串长度len为6和4的情况。再记录一下len为4的时候,前两个字符串的整数值,若小于22则输出20,否则输出19。其他字符只需要按照模板直接输出即可。

#include
#include
using namespace std;
int main(){
    char str[10];
    cin>>str;
    int len=strlen(str);
    if(len==6) {
    	for(int i=0;i<4;i++) cout<<str[i];
    	cout<<"-";
    	for(int i=4;i<6;i++) cout<<str[i];
	}
	else{
		int n=(str[0]-'0')*10+(str[1]-'0');
		if(n<22) cout<<"20";
		else cout<<19;
		cout<<str[0]<<str[1]<<"-"<<str[2]<<str[3]; 
	}
    return 0;
}

L1-04 降价提醒机器人 (10 分)

分析:
注意一下输出保留一位小数的格式即可。

#include
using namespace std;
int main(){
    int n,m;
    double c;
    cin>>n>>m;
    for(int i=1;i<n;i++){
        cin>>c;
        if(c<m) printf("On Sale! %.1lf\n",c);
    }
    cin>>c;
    if(c<m) printf("On Sale! %.1lf",c);
    return 0;
}

L1-05大笨钟的心情 (15 分)

分析:
注意一下结尾无空行,用一个数组a[30]存储0-23小时的心情,然后每一小时t的心情便是a[t]。

#include
using namespace std;
int main(){
    int a[30],x;
    for(int i=0;i<=23;i++) cin>>a[i];
    cin>>x;
    if(x<0||x>23) return 0;
    else {
        cout<<a[x]<<" ";
        if(a[x]>50) cout<<"Yes";
        else cout<<"No";
    };
    while(cin>>x){
        if(x<0||x>23) break;
        else{
            cout<<"\n"<<a[x]<<" ";
            if(a[x]>50) cout<<"Yes";
            else cout<<"No";
        }
    }
    return 0;
}


L1-06 吉老师的回归 (15 分)

分析:
难点在于输入。需要输入可以输入一整行字符串包括有空格,所以可用gets()和getline()函数,当然记得用getchar()吃回车,不然输入会出错。

#include
using namespace std;
int main(){
    int n,m;cin>>n>>m;
    string str;
    queue<string>q;
    getchar();
    for(int i=1;i<=n;i++){
        getline(cin,str);
        if(str.find("easy")!=-1||str.find("qiandao")!=-1) continue;
        else q.push(str);
    }
    while(!q.empty()&&m--){
    	q.pop();
	}
	if(q.empty()) cout<<"Wo AK le";
	else cout<<q.front();	
    return 0;
}


L1-07天梯赛的善良 (20 分)

分析:
函数sort给序列a[1…n]排个序,那么第一个a[1]则是最小的,最后一个a[n]则是最大的。然后遍历一遍,分别记录值为a[1]和a[n]的个数。

#include
using namespace std;
const int maxn=2e4+10;
int a[maxn];
int main(){
    int n,num1=0,num2=0;cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++){
        if(a[i]==a[1]) num1++;
        if(a[i]==a[n]) num2++;
    }
    cout<<a[1]<<" "<<num1<<"\n"<<a[n]<<" "<<num2;
    return 0;
}

L1-08 乘法口诀数列 (20 分))

分析:
因为每一个元素的范围是0-9,所以两个数的乘积最大为81,所以对于每一次乘积分别考虑它为个位数和十位数即可。再从i=2开始遍历,用x记录乘积值a[i]*a[i-1]

#include
using namespace std;
int main(){
    int a[1005],n,k=2,i=2;
    cin>>a[1]>>a[2]>>n;
    while(k<=n){
        int x=a[i]*a[i-1];
        if(x>=10) a[++k]=x/10,a[++k]=x%10;
        else a[++k]=x;
        i++;
    }
    for(int i=1;i<n;i++) cout<<a[i]<<" ";
    cout<<a[n];
    return 0;
}

L2-01 包装机 (25 分)

分析:
考察栈和队列的应用。通过题意可知,框服从后进先出的原则,而轨道和流水线都服从先进先出的原则;所以可用栈来维护框中的元素,用队列来维护轨道和流水线中的元素。再不断输入一个整数x,分别讨论x==-1;x==0以及x为其他值的情况即可。

#include
using namespace std;
int main(){
	int N,M,S,x;
	cin>>N>>M>>S;
	queue<char>q,qs[105];
	stack<char>s;
	for(int i=1;i<=N;i++){
		for(int j=1;j<=M;j++){
			char str;cin>>str;
			qs[i].push(str);
		}
	}
	while(cin>>x){
		if(x==-1) break;
		else if((x==0&&!s.empty())){
			q.push(s.top()); s.pop();
		}
	    else {
	    	if(qs[x].empty()) continue;
	    	if((s.size()==S&&!s.empty())){
	    		q.push(s.top());
			    s.pop();
			}
	    	s.push(qs[x].front()); qs[x].pop();
		}
	}
	while(!q.empty()) {
		cout<<q.front();
		q.pop();
	}
	return 0;
} 

L2-02 病毒溯源 (25 分)

分析:
显然这是一道深搜题(dfs)。只需要先找到病毒的源头,再对它进行深搜便可以很快得到结果。那么如何找到病毒的源头呢?由于题目提示,所有的病毒都是靠变异而来,那么从0…n-1序列中唯一一个没有在编译序列中出现的元素便是病毒源头。由于题目还说明了要输出序列元素最小的最长链,那么对于每一个元素的变异序列我们可以用邻接表来维护,并每一个元素的邻接表通过函数sort()升序,这样在dfs过程中便可以保证第一次搜到的最长串便是元素最小的。

#include
using namespace std;
const int maxn=1e4+10;
int a[maxn],maxx=0,visit[maxn];
vector<int>g[maxn],ans;
void Init(){
	memset(visit,0,sizeof(visit));
	for(int i=1;i<=maxn;i++) g[i].clear();
}
void dfs(int u,int step){
	a[step]=u;
	if(g[u].size()==0){
		if(step>maxx) {
			maxx=step; ans.clear();
			for(int i=1;i<=step;i++) ans.push_back(a[i]);
		}
	}
	else{
		for(int i=0;i<g[u].size();i++){
		int v=g[u][i]; dfs(v,step+1);
	    }
	}
}
int main(){
    int n,pos; cin>>n;
    Init();
    for(int i=0;i<n;i++){
    	int opt;cin>>opt;
        while(opt--){
        	int x;cin>>x;
        	g[i].push_back(x);
        	visit[x]=1;
		}
		sort(g[i].begin(),g[i].end());
	}
	for(int i=0;i<n;i++) if(!visit[i]) {
		pos=i; break;
	}
	dfs(pos,1);
	cout<<maxx<<"\n"<<*(ans.begin());;
	for(vector<int>::iterator it=ans.begin()+1;it!=ans.end();it++) cout<<" "<<*it;
	return 0;
}

L2-03 清点代码库 (25 分)

分析:
非常考察STL容器的熟悉应用。本题我用到了三个容器,vector,pair和map。首先通过输入我们可以发现,我们可以用vector来维护每一个模块,然后我们需要维护一个map容器将vector映射到该模块出现的次数上。显然,map容器的大小就是模块的个数。那么如何按照要求让模块出现次数从大到小,模块出现次数相同的模块从小到大输出呢?我们不妨来分析一下孰轻孰重啊。很显然,一个元素的排序是比一个模块的排序简单得多,所以我们可以考虑,在维护容器map的时候就先维护一下模块的排序。那么如何维护呢?STL中默认容器mapmp的内部结构是按照key从小到大排序。并且mp.first的值就是key的值,mp.second的值就是value的值。所以我们可以创建容器map为map;这样我们就已经维护好了模块是升序的。那接下我们如何再维护value是降序的呢?我们需要将map中的元素输出到vector中,通过pair进行排序。所以维护一个vector容器,内部的元素为pair对,该对的第一个元素为模块出现次数,第二个元素为模块;即vector > >ans;为什么要让第一个元素是模块出现次数呢?因为STL中规定对vector > >ans进行排序时,默认是对pair对的first元素进行排序。所以我们可以借助sort()函数再对该vector容器进行排序。但是最后又如何让容器中的第一个元素(模块出现次数)降序排序,而第二个元素(模块)继续保持相对升序呢?我们可以在map中的元素输出到vector的时候,将模块次数的相反数输出。最后sort(ans.begin(),ans.end())给vector升序。这样就可以保持模块的相对升序次序不变,并且对模块出现次数的相反数升序排序。最后输出的时候再输出模块相反数的相反数即可。

#include
using namespace std;
const int maxn=2e5+10;
map<vector<int>,int>mp;
vector<pair<int,vector<int> > >v;
inline int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	return x*f;
}

int main(){
	int n,m,k=0;
	n=read();m=read();
	for(int i=1;i<=n;i++){
		vector<int>temp;
		int x;
		for(int j=1;j<=m;j++){
			x=read(); temp.push_back(x);
		}
	    mp[temp]++;
	}
	printf("%d\n",mp.size());
	for(auto it:mp) v.push_back({-it.second,it.first});
    sort(v.begin(),v.end());
	for(auto it:v){
		printf("%d",-it.first);
		vector<int>ans=it.second;
		for(auto i:ans) printf(" %d",i);
		printf("\n");
	}
	return 0;
} 

`

L2-04 哲哲打游戏 (25 分)

分析:
题目很简单,难点在于读懂题。可用vector维护每一个剧情点每一个操作的结果,也可用二维数组维护;再额外开一个数组维护存档的值,分别讨论操作类型为0、1和2的情况即可。

#include
using namespace std;
const int maxn=1e5+10;
int book[maxn],ans=1;
vector<int>g[maxn];
int main(){
	int n,m; cin>>n>>m;
	for(int i=1;i<=n;i++){
		int opt; cin>>opt;
		while(opt--){
			int x;cin>>x;
			g[i].push_back(x);
		}
	}
	for(int i=1;i<=m;i++){
		int opt,x;cin>>opt>>x;
		if(opt==0)	ans=g[ans][x-1];
		else if(opt==1) {
			book[x]=ans;
		    cout<<ans<<endl;
		}
		else ans=book[x];
	}
	cout<<ans;
	return 0;
}

L3-01 森森旅游 (30 分)

分析:
看完题,相信大多数人就已经能想到本题大致的算法是Dijkstra算法。我们可以对每一个城市i来分析,很显然如果在城市i兑换了旅游金,那么1…i之间用的都是现金c, 城市i…n之间用的都是旅游金d。所以我们需要维护两个单源最短路,并且是两个相反的单源最短路;即一条是从起点1开始到任意节点的单源最短路,权值为现金c;我们记录该路为d1[maxn]。另一条是从起点n开始到任意节点的单源最短路,权值为旅游金d,我们记录为d2[maxn]。这样我们便可以求出在任意一个结点i兑换了旅游金以后花费的最少现金ans[i],即d1[i]+(d2[i]+d[i]-1)/a[i];。我们可以用一个mutliset容器来维护每一个ans[i];为啥用mutliset呢?因为它默认升序并且可以有重复元素。然后在更改汇率的时候,我们只需要先找到需要更改汇率的城市x,将它之前的花费ans[x]从容器中删除再添加汇率改变后新的花费ans[x]'即可;最后输出容器中第一个元素便是答案。
时间复杂度:O(2*mlogn+qlogn)
蒻蒻只得了21分,玄学错误看不明白;希望看到错误的大佬可以提醒我。

#include
using namespace std ;
const int maxn=1e5+10;
typedef long long ll;
const ll INF=1e18;
multiset<ll>ms;
struct Edge{
	ll v,w;
	bool operator <(const Edge&b) const{
		return w>b.w;
	};
};
vector<Edge>gc[maxn],gd[maxn];
ll n,m,q,a[maxn],d1[maxn],d2[maxn],visit[maxn],ans[maxn];
inline ll read(){
	ll x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	return x*f;
}
void Init(){
	memset(visit,0,sizeof(visit));
}
void Dijkstar(vector<Edge>g[],ll d[],ll s){
	for(int i=1;i<=n;i++) d[i]=INF;
	priority_queue<Edge>q;
	q.push({s,0});
	d[s]=0;
	while(!q.empty()){
		Edge e=q.top();q.pop();
		ll u=e.v;
		if(visit[u]) continue;
		visit[u]=1;
		for(int i=0;i<g[u].size();i++){
			ll v=g[u][i].v,w=g[u][i].w;
			if(w+d[u]<d[v]) {
				d[v]=w+d[u];
				q.push({v,w});
			}
		}
	}
}
int main(){
	memset(ans,0,sizeof(ans));
    n=read(),m=read(),q=read();
    for(int i=1;i<=m;i++){
    	int u=read(),v=read(),c=read(),d=read();
    	gc[u].push_back({v,c});
    	gd[v].push_back({u,d});
	}
	Init();    Dijkstar(gc,d1,1);
	Init();    Dijkstar(gd,d2,n);
	for(int i=1;i<=n;i++) a[i]=read();	
	for(int i=1;i<=n;i++){
		if(d1[i]==INF||d2[i]==INF) continue;
		ans[i]=d1[i]+(d2[i]+a[i]-1)/a[i];
		ms.insert(ans[i]);
	}
	for(int i=1;i<=q;i++){
		ll x=read(),y=read();
		if(d1[x]!=INF&&d2[x]!=INF) {
			ll res=d1[x]+(d2[x]+a[x]-1)/a[x]; ms.erase(ms.find(res));
		    a[x]=y; ms.insert(d1[x]+(d2[x]+a[x]-1)/a[x]);
		}
		multiset<ll>::iterator it=ms.begin();
		if(i!=q) cout<<*it<<endl;
		else cout<<*it;
	}
    
	return 0;
}


L3-02 还原文件 (30 分)

分析:
蒻蒻太菜了,看完大佬的思想才知道这道题可以使用dfs写。用dfs的思路就很简单了;我们可以搜索每一个折线角(实际上只会搜索到每一个碎片的起始折线角)。然后直接暴力比较每一个碎片,看看该碎片的每一个折线角是否满足要求;若是满足要求则记录该碎片的序号并标记为已访问。;按照这样可以得26分,那另外4分又是怎么回事呢? 我们可以考虑这样一种情况,如果一个碎片是另一个碎片的前缀,那么结果是不是可能紊乱呢?答案是肯定的;所以我们还需要回溯再搜索,也就是加上我在代码中注释的那一行即可。
时间复杂度:大概是O(m*n)。

#include
using namespace std;
const int maxn=1e5+10;
int h[maxn],n,m;
bool vis[maxn],opt=false;
vector<int>v[maxn],ans;
void dfs(int pos){ //当前在第pos个折线角位置 
    if(opt) return ;
	if(pos>=n){
		opt=true;
		cout<<*(ans.begin());
		for(vector<int>::iterator it=ans.begin()+1;it!=ans.end();it++) cout<<" "<<*it;
		return ;
	}
	for(int i=1;i<=m;i++){
		if(vis[i]) continue;
		bool flag=true;
		for(int j=0;j<v[i].size();j++){
			if(h[pos+j]!=v[i][j]) {flag=false;break;}
		}
		if(!flag) continue;
		ans.push_back(i);vis[i]=true;
		dfs(pos+v[i].size()-1);
		ans.pop_back();vis[i]=false; //不加这一行回溯的话只能拿26分
	}
}
int main(){
	iostream::sync_with_stdio(false);
	cin>>n;
	for(int i=1;i<=n;i++) cin>>h[i];
	cin>>m;
	for(int i=1;i<=m;i++) vis[i]=false;
    for(int i=1;i<=m;i++){
    	int k,x;cin>>k;
    	while(k--) {cin>>x;v[i].push_back(x);}
	}
	dfs(1); 
	return 0;
}

你可能感兴趣的:(题目,c语言,c++,开发语言)