2024寒假集训 进阶训练赛 (十六)部分题解

文章详细介绍了编程问题中的快速幂乘、求最右边数字的算法,以及士兵队列训练、双端队列操作、集合合并和树的子结点计数等数据结构和算法的应用实例。
摘要由CSDN通过智能技术生成

目录

问题 A: 快速幂乘

问题 B: Rightmost Digit

问题 E: 2.4.4 士兵队列训练

问题 F: 2.4.5 度度熊学队列

问题 H: 2.4.8.1 集合合并 

问题 J: 2.4.9.1 硬木种类 

问题 K: 鬼抓人

问题 L: 坠落之前 

问题 M: 数据结构:树的子结点计数 


问题 A: 快速幂乘

题目描述

求整数A的B次幂。结果给出最后三位。

输入

A B
(0<A,B<=1000)

输出

A的B次幂

样例输入

2 3

样例输出

8

思路

运用了快速幂以及快速乘的方法,具体思路请见以下链接:

【基本算法】快速幂和快速乘法详解

参考代码

#include <iostream>
using namespace std;

long long Quickmul(long long a,long long b){
	long long res=0;
	while(b){
		if(b%2) res=(res+a)%1000;
		a=(a+a)%1000;
		b/=2;
	}
	return res;
}

long long Quickpow(long long a,long long b){
	long long res=1;
	while(b){
		if(b%2) res=Quickmul(res,a)%1000;
		a=Quickmul(a,a)%1000;
		b/=2;
	}
	return res;
}

int main(){
	long long a,b;
	cin>>a>>b;
	long long ans=Quickpow(a,b);
	cout<<ans<<endl;
	return 0;
}

问题 B: Rightmost Digit

题目描述

给定一个正整数N,应该输出N^N的最右边的数字。

输入

包含几个测试样例。输入的第一行是一个整数T,他是测试样例的数量。接下来是T个测试样例。每个测试样例都包含一个正整数(1<=N<=1e8)

输出

对于每个测试样例,应该输出N^N的最右边的数字。

样例输入

2
3
4

样例输出

7
6

思路

同上一题,运用了快速幂以及快速乘的方法,这里是求个位

参考代码

#include <iostream>
using namespace std;

long long Quickmul(long long a,long long b){
	long long res=0;
	while(b){
		if(b%2) res=(res+a)%10;
		a=(a+a)%10;
		b/=2;
	}
	return res;
}

long long Quickpow(long long a,long long b){
	long long res=1;
	while(b){
		if(b%2) res=Quickmul(res,a)%10;
		a=Quickmul(a,a)%10;
		b/=2;
	}
	return res;
}

int main(){
	int t;
	cin>>t;
	while(t--){
		long long n;
		cin>>n;
		cout<<Quickpow(n,n)<<endl;
	}
	return 0;
}

问题 E: 2.4.4 士兵队列训练

题目描述

某部队进行新兵队列训练,将新兵从一开始按顺序依次编号,并排成一行横队,训练的规则如下:从头开始1至2报数,凡报到2的出列,剩下的向小序号方向靠拢,再从头开始进行1至3报数,凡报到3的出列,剩下的向小序号方向靠拢,继续从头开始进行1至2报数······以后从头开始轮流进行1至2报数、1至3报数直到剩下的人数不超过三人为止。

输入

本题有多个测试数据组,第一行为组数N,接着为N行新兵人数,新兵人数不超过10000。

输出

共有N行,分别对应输入的新兵人数,每行输出剩下的新兵最初的编号,编号之间有一个空格。

样例输入

2 
20 
40

样例输出

1 7 19
1 19 37

思路 

 本题是对队列的考察,在队列中进行循环操作,直到剩下的人数不超过三人:

  • 依次出列一个人,编号为1的新兵插入队尾,并按照1至2报数规则出列一部分人。
  • 再次出列一个人,编号为1的新兵插入队尾,并按照1至3报数规则出列一部分人。
  • 输出剩下的新兵最初的编号。

参考代码 

#include <iostream>
#include <queue>
using namespace std;

int main(){
	int n;
	cin>>n;
	queue<int> q;
	while(n--){
		int m;
		cin>>m;
		for(int i=1;i<=m;i++) q.push(i);
		while(true){
			if(q.size()<=3) break;
			q.pop();
			q.push(1);
			for(int i=2;q.front()!=1;i++){
				if(i%2!=0) q.push(q.front());
				q.pop();
			}
			if(q.size()<=3) break;
			q.pop();
			q.push(1);
			for(int i=2;q.front()!=1;i++){
				if(i%3!=0) q.push(q.front());
				q.pop();
			}
		}
		while(!q.empty()){
			cout<<q.front()<<" ";
			q.pop();
		}
		cout<<endl;
	}
	return 0;
}

问题 F: 2.4.5 度度熊学队列

题目描述

度度熊正在学习双端队列,他对其翻转和合并产生了很大的兴趣。 初始时有 N 个空的双端队列(编号为 1 到 N ),你要支持度度熊的 Q 次操作。

①1 u w val 在编号为 u 的队列里加入一个权值为 val 的元素。(w=0表示加在最前面,w=1 表示加在最后面)。

②2 u w 询问编号为 u 的队列里的某个元素并删除它。( w=0 表示询问并操作最前面的元素,w=1 表示最后面)

③3 u v w 把编号为 v 的队列“接在”编号为 u 的队列的最后面。w=0 表示顺序接(队列 v 的开头和队列 u 的结尾连在一起,队列v 的结尾作为新队列的结尾), w=1 表示逆序接(先将队列 v 翻转,再顺序接在队列 u 后面)。且该操作完成后,队列 v 被清空。

输入

有多组数据。

对于每一组数据,第一行读入两个数 N 和 Q。

接下来有 Q 行,每行 3~4 个数,意义如上。

N≤150000,Q≤400000

1≤u,v≤N,0≤w≤1,1≤val≤100000

所有数据里 Q 的和不超过500000

输出

对于每组数据的每一个操作②,输出一行表示答案。

注意,如果操作②的队列是空的,就输出-1−1且不执行删除操作。

样例输入

2 10
1 1 1 23
1 1 0 233
2 1 1 
1 2 1 2333
1 2 1 23333
3 1 2 1
2 2 0
2 1 1
2 1 0
2 1 1

样例输出

23
-1
2333
233
23333

思路

本题使用双端队列(deque)求解 

参考代码 

#include <iostream>
#include <deque>
#include <vector>
using namespace std;

int main(){
	int N,Q;
	cin>>N>>Q;
	vector<deque<int>> q(N+1);
	for(int i=0;i<Q;i++){
		int op,u,v,w,val;
		cin>>op>>u;
		if(op==1){
			cin>>w>>val;
			if(w==0) q[u].push_front(val);
			else q[u].push_back(val);
		}
		else if(op==2){
			cin>>w;
			if(q[u].empty()) cout<<-1<<endl;
			else{
				if(w==0){
					cout<<q[u].front()<<endl;
					q[u].pop_front();
				} 
				else{
					cout<<q[u].back()<<endl;
					q[u].pop_back();
				}
			}
		}
		else if(op==3){
			cin>>v>>w;
			if(w==0){
				while(!q[v].empty()){
					q[u].push_back(q[v].front());
					q[v].pop_front();
				}
			}
			else{
				while(!q[v].empty()){
					q[u].push_back(q[v].back());
					q[v].pop_back();
				}
			}
		}
	}
	return 0;
}

问题 H: 2.4.8.1 集合合并 

题目描述

给定两个集合A,B 求A∪B 

输入

输入有多组(不定)的数据
输入每组数据三行
每组第一行,两个整数,n,m (0<n,m≤10 000)
第二行 n个整数 代表集合A中的数字 (在int范围内)
第三行 m个整数 代表集合B中的数字 (在int范围内)

输出

输出每组数据输出在一行,代表合并后的集合的每个元素,要求从小到大输出

样例输入

1 2
1
2 3
1 2
1
1 2

样例输出

1 2 3
1 2

思路 

本题使用set求解(我看以前的题解是这样写的)

参考代码 

#include <iostream>
#include <set>
using namespace std;

int main(){
	int n,m;
	while(cin>>n>>m){
		set<int> s;
		for(int i=0;i<n+m;i++){
			int x;
			cin>>x;
			s.insert(x);
		}
		for(auto it=s.begin();it!=s.end();it++) cout<<*it<<" ";
		cout<<endl;
	}
	return 0;
}

问题 J: 2.4.9.1 硬木种类 

题目描述

某国有数百种硬木树种,该国自然资源部利用卫星成像技术编制了一份特定日期每棵树的物种清单。计算每个物种占所有种群的百分比。

输入

输入包括每棵树的物种清单,每行一棵树。物种名称不超过30个字符,不超过10000种,不超过1000000棵树。

输出

按字母顺序输出植物种群中代表的每个物种的名称,然后使占所有种群的百分比,保留小数点后4位。

样例输入

Red Alder
Ash
Aspen
Basswood
Ash
Beech
Yellow Birch
Ash
Cherry
Cottonwood
Ash
Cypress
Red Elm
Gum
Hackberry
White Oak
Hickory
Pecan
Hard Maple
White Oak
Soft Maple
Red Oak
Red Oak
White Oak
Poplan
Sassafras
Sycamore
Black Walnut
Willow

样例输出

Ash 13.7931
Aspen 3.4483
Basswood 3.4483
Beech 3.4483
Black Walnut 3, 4483
Cherry 3.4483
Cottonwood  3.4483
Cypress 3.4483
Gum 3.4483
Hackberry 3.4483
Hard Maple 3.4483
Hickory 3.4483
Pecan 3.4483
Poplan 3.4483
Red Alder 3.4483
Red Elm 3.4483
Red Oak 6.8966
Sassafras 3.4483
Soft Maple 3.4483
Sycamore 3.4483
White Oak 10.3448
Willow 3.4483
Yellow Birch 3.4483

思路 

使用map容器累计每个树种出现的次数,插入所有树种的字符串,正向遍历求解

参考代码

#include <iostream>
#include <map>
using namespace std;

int main(){
	string name;
	map<string,double> tree;
	int count=0;
	while(getline(cin,name)){
		tree[name]++;
		count++;
	}
	for(auto it=tree.begin();it!=tree.end();it++){
		float res=(100.0*it->second)/count;
		cout<<it->first<<" ";
		printf("%.4f\n",res);
	}
	return 0;
}

问题 K: 鬼抓人

题目描述

糖豆人小科普:
鬼抓人游戏规则——规定时间内将对手全部感染为“鬼”或时间结束时队内未被感染的“人”数量多于对手为胜
已知当前我队未被感染的“人”数量为 n ,对手已被感染的“鬼”数量为 m
若对手已被感染的“鬼”数量大于等于我队未被感染的“人”数量,则当前剩余“人”处境危险,输出unsafe,否则输出safe

输入

一行包括两个正整数n(1≤n≤100) 和 m(1≤m≤100)

输出

若当前剩余“人”处境危险,输出unsafe
否则输出safe

样例输入

4 5

样例输出

unsafe

思路 

 比个大小即可

参考代码 

#include <iostream>
using namespace std;

int main(){
	int n,m;
	cin>>n>>m;
	if(m>=n) cout<<"unsafe"<<endl;
	else cout<<"safe"<<endl;
	return 0;
}

问题 L: 坠落之前 

题目描述

小 q 每天的目标是活过这周并且成功抵达周末
但是对于小 q 到达周末,有着 M 项 ddl 阻碍着她
小 q 心里有个小小的愿望,她希望在工作日写作业的间隙可以偷偷玩一玩她最喜欢的糖豆人
(((每能在工作日游玩一次糖豆人,就可以拯救一只即将累死的小 q !
现在已知工作日剩余时间为 N,小 q 还剩下 M 项 ddl 未完成,
对于每项 ddl ,小 q 需要花费连续的 Ai 时间完成它
请帮小 q 算算工作日内还有多少时间可以偷偷玩糖豆人,
如果小 q 在工作日内无法完成所有 M项 ddl,请输出 −1 (((得到一只伤心致死的小 q ╭(°A°`)╮

输入

第一行两个正整数 N、M(空格间隔) ,分别表示工作日剩余时间以及剩余 ddl 数量(1≤N≤10^6,1≤M≤10^4)
第二行包含 M 个正整数 Ai (空格间隔),表示第 i 项 ddl 所需要花费的时间(1≤Ai≤10^4)

输出

一个整数表示工作日内小 q 可游玩糖豆人的时间
如果小 q 在工作日内无法完成所有 M 项 ddl,输出 −1

样例输入

41 2
5 6

样例输出

30

思路 

用剩余时间减去ddl所需时间,再判断是否小于0得到输出

参考代码

#include <iostream>
using namespace std;

int main(){
	int n,m;
	cin>>n>>m;
	int a[m];
	for(int i=0;i<m;i++){
		cin>>a[i];
		n-=a[i];
	}
	if(n<0) cout<<-1<<endl;
	else cout<<n<<endl;
	return 0;
}

问题 M: 数据结构:树的子结点计数 

题目描述

树(tree)是 n(n>0) 个结点的有限集T,其中

有且仅有一个特定的结点,称为树的根(root) 关于树有几个基本术语:

  • 孩子(child)——结点子树的根称为该结点的孩子
  • 双亲(parent)——孩子结点的上层结点
  • 根(root)——没有双亲的结点

而对于一棵树,有三种存储结构

  • 双亲表示法
  • 孩子表示法
  • 孩子兄弟表示法

现在我们给出一个结点数为N 的树,其根结点为 1.

接下来,我们使用双亲表示法来表示这颗树:给出一个数组 A , Ai代表结点i的双亲是 Ai

接下来,对于每个结点i=1,2,...,N你需要求出结点i 的孩子结点数量

输入

第一行包括一个正整数 N(2<=N<=2*10^5)

第二行包括 N-1个正整数 Ai(1<=Ai<=i),(i=2,3,...N) ,以空格间隔

输出

答案共 N行,

第 i行表示结点i(1<=i<=N)的孩子结点数量。

样例输入

5
1 1 2 2

样例输出

2
2
0
0
0

提示

如图,结点 1 的孩子为 2,3 ,结点 2 的孩子为 4,5

参考代码

#include <iostream>
using namespace std;

int main(){
	int N;
	cin>>N;
	int parent[N+1];
	for(int i=2;i<=N;i++) cin>>parent[i];
	int count[N+1]={0};
	for(int i=2;i<=N;i++) count[parent[i]]++;
	for(int i=1;i<=N;i++) cout<<count[i]<<endl;
	return 0;
}

  • 23
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值