数据结构荣誉课-第六次实验-解题报告


这是第一次综合上机实验

一、高精度加法

题目

高精度数是指大大超出了标准数据类型能表示的范围的数,例如10000位整数。很多计算问题的结果都很大,因此,高精度数极其重要。

一般使用一个数组来存储高精度数的所有数位,数组中的每个元素存储该高精度数的1位数字或多位数字。 请尝试计算:N个高精度数的加和。这个任务对于在学习数据结构的你来说应该是小菜一碟。 。

输入格式:

第1行,1个整数N,表示高精度整数的个数,(1≤N≤10000)。

第2至N+1行,每行1个高精度整数x, x最多100位。

输出格式:

1行,1个高精度整数,表示输入的N个高精度数的加和。

输入样例:

3
12345678910
12345678910
12345678910

输出样例:

37037036730

解题思路

之前在C++实验课的时候包括在上学期的时候也做过了类似的题,这种高精度加法,需要用数组来存储,整数的每一位分别存在数组的每一个元素中,即用ans[i]存储10^i上的数字,还有一个关键就是,在相加时需要将正数倒着存储在数组中,这样方便应对相加时产生的进位。

参考代码

#include<iostream>
#include<cstdio>
#include<cstring> 
#include<algorithm>

using namespace std;

const int maxn = 500;

char str[maxn];
int ans[maxn]; // 用于存正数相加的结果
int bns[maxn]; // 用于存负数相加的结果
int buff[maxn];
int len2 = 0;  //正 
int len1 = 0;  //负 

void add(){
	int length = strlen(str);
	for(int i = 0; i < maxn; i++) buff[i] = 0;
	
	if(str[0] == '-'){
		if(length > len1) len1 = length;
		for(int i = 0; i < length - 1; i++){
			buff[i] = str[length-i-1] - '0';
		}
		len1--;
		for(int i = 0; i < len1; i++){
			bns[i] += buff[i];
    		bns[i+1] += bns[i] / 10;
	    	bns[i] %= 10;
		}
		if(bns[len1]!=0) len1 ++;
	}
	else
	{
		if(length > len2) len2 = length;
		for(int i = 0; i < length; i++){
			buff[i] = str[length-i-1] - '0';
		}
		for(int i = 0; i < len2; i++){
			ans[i] += buff[i];
    		ans[i+1] += ans[i] / 10;
	    	ans[i] %= 10;
		}
		if(ans[len2]!=0) len2 ++;
	}
}
bool judge(){
	if(len1 != len2) return len1 > len2;
	for(int i = len1 - 1; i >= 0; i --) {
		if(bns[i] != ans[i]) {
			return bns[i] > ans[i]; 
		} 
	}
	return false; 
}
int main(){
	int n;
	scanf("%d",&n);
	for(int i = 0; i < maxn; i++) ans[i] = bns[i] = 0;
	for(int i = 0; i < n; i++){
		scanf("%s",str);
		add();
	}
	if(!judge()){                      //ans>bns结果为正 
		for(int i = 0; i < len1; i ++) {
	    	ans[i] -= bns[i];
	    	while(ans[i] < 0) {
		    	ans[i] += 10;
			    ans[i + 1] --;
	    	}
    	}
    	while(len2 > 0 && ans[len2 - 1] == 0) {
	    	len2 --;
    	}
    	if(len2==0) printf("0");
    	else{
    		for(int i = len2 - 1 ; i >= 0; i --) {
				printf("%d", ans[i]);
			}
		}
    	
	}
	else{                              //bns>ans结果为负 
		for(int i = 0; i < len2; i ++) {
	    	bns[i] -= ans[i];
	    	while(bns[i] < 0) {
		    	bns[i] += 10;
			    bns[i + 1] --;
	    	}
    	}
    	while(len1 > 0 && bns[len1 - 1] == 0) {
	    	len1 --;
    	}
    	printf("-");
    	for(int i = len1 - 1 ; i >= 0; i --) {
				printf("%d", bns[i]);
			}
	}
	return 0;
}

二、二叉树加权距离

题目

二叉树结点间的一种加权距离定义为:上行方向的变数×3 +下行方向的边数×2 。上行方向是指由结点向根的方向,下行方向是指与由根向叶结点方向。 给定一棵二叉树T及两个结点u和v,试求u到v的加权距离。

输入格式:

第1行,1个整数N,表示二叉树的结点数,(1≤N≤100000)。

随后若干行,每行两个整数a和b,用空格分隔,表示结点a到结点b有一条边,a、b是结点的编号,1≤a、b≤N;根结点编号为1,边从根向叶结点方向。

最后1行,两个整数u和v,用空格分隔,表示所查询的两个结点的编号,1≤u、v≤N。

输出格式:

1行,1个整数,表示查询的加权距离。

输入样例:

5
1 2
2 3
1 4
4 5
3 4

输出样例:

8

解题思路

这道题最开始做的时候其实有点没看懂题,以为是两个点分别到根节点的边数 ,在讲题的时候听老师说最近公共祖先的时候才恍然大悟,想起来这两个节点还可能都是在根节点的左子树或者右子树上,这才明白之前碰运气的代码为什么才过了一半。
在课后,再打这道题的时候,写了一个 lca 函数来求最近公共祖先,虽然测试样例过了,但是在平台上每次都有一个测试点显示内存超限,我就怀疑是不是我用的 vector 的问题,因为 vector 是一个动态的,于是我又改用邻接链表,结果还是超限,最后我想到了因为是一个树,所以转换为无向图来看的话每个节点的邻接顶点也超不过三个,于是我改用一个结构体
struct Vertex{
int count;
int edge[3];//边链表头指针
}
来表示节点及其邻接顶点,用 count 来表示邻接顶点的个数。这样将有关的 dfs 函数修改,就全都过了。
代码如下:

参考代码

#include<iostream>
#include<cstdio>
#include<algorithm>
//#include<vector> 

using namespace std;

const int maxn = 100001;

//vector<int> edge[maxn];
//typedef struct Edge{//边结点的结构体 
//	int VerAdj;//邻接顶点的序号,用自然数编号 
//	//int cost;//边的权值 
//	//struct Edge* link;//指向下一个边结点的指针 
//}Edge;

typedef struct Vertex{//顶点表中的结点的结构体 
	//int VerName;//顶点的名称 
	int count;
	int edge[3];//边链表头指针 
}Vertex;

Vertex Head[maxn];
int N;   //树的节点数 

int dep[maxn];
int father[maxn];
//int vis[maxn];

void addedge(int f,int t){
	Head[f].edge[Head[f].count] = t;
	Head[t].edge[Head[t].count] = f;
	Head[f].count++;
	Head[t].count++;
}
int lca(int a,int b){ //求最近公共祖先 
	if(dep[a] >= dep[b]){
		while(dep[a] > dep[b]){
			a = father[a];
		}
		while(a != b){
			a = father[a];
			b = father[b];
		}
		return a;
	}
	else{
		while(dep[b] > dep[a]){
			b = father[b];
		}
		while(a != b){
			a = father[a];
			b = father[b];
		}
		return b;
	} 
}
//void dfs(int x,int f){
//	father[x] = f;
//	dep[x] = dep[f] + 1;
//	for(int i = 0; i < edge[x].size(); i++){
//		int temp = edge[x][i];
//		if(temp == f) continue;
//		dfs(temp,x);
//	}
//}

void dfs(int x,int f){
	father[x] = f;
	dep[x] = dep[f] + 1;
	for(int i = 0; i < Head[x].count; i++){
		int temp = Head[x].edge[i];
		if(temp == f) continue;
		dfs(temp,x);
	}
}
int main(){
	for(int i = 0; i < maxn; i++) { Head[i].count = 0;}
    scanf("%d",&N);
	for(int i = 1; i <= N-1; i++){
		int a,b;
		scanf("%d%d",&a,&b); addedge(a,b);
	}
	
	for(int i = 0; i < maxn; i++) dep[i] = 0; //二叉树深度 
	dfs(1,0);
	int u,v;
	scanf("%d%d",&u,&v);
	int x = lca(u,v);
	printf("%d",(dep[u]-dep[x])*3+(dep[v]-dep[x])*2);
	return 0;
}

三、修轻轨

题目

长春市有n个交通枢纽,计划在1号枢纽到n号枢纽之间修建一条轻轨。轻轨由多段隧道组成,候选隧道有m段。每段候选隧道只能由一个公司施工,施工天数对各家公司一致。有n家施工公司,每家公司同时最多只能修建一条候选隧道。所有公司可以同时开始施工。请评估:修建这条轻轨最少要多少天。。

输入格式:

第1行,两个整数n和m,用空格分隔,分别表示交通枢纽的数量和候选隧道的数量,1 ≤ n ≤ 100000,1 ≤ m ≤ 200000。

第2行到第m+1行,每行三个整数a、b、c,用空格分隔,表示枢纽a和枢纽b之间可以修建一条双向隧道,施工时间为c天,1 ≤ a, b ≤ n,1 ≤ c ≤ 1000000。

输出格式:

输出一行,包含一个整数,表示最少施工天数。

输入样例:

6 6
1 2 4
2 3 4
3 6 7
1 4 2
4 5 5
5 6 6

输出样例:

6

解题思路

这个题的意思是将 1 和 n 连通,所以我认为它俩应该在一个连通分量中,所以就想到了并查集的做法,又因为题目要求求最小的施工天数,也就是求 1 和 n 所在的连用分量中的边的最大权值,所以我又想到了克鲁斯卡尔的算法,老师还提醒了说不一定所有的枢纽都要连通,也就是克鲁斯卡尔的一个小变形,在遍历所有边的时候只要满足 1 和 n 在一个连通分量中即可。接下来就是克鲁斯卡尔的算法的实现,不在赘述。

参考代码

#include<iostream>
#include<cstdio>
#include<algorithm>

using namespace std;

const int maxn = 200001;

struct Edge{
	int f,t,v;
}edge[maxn];

int father[maxn];

void make_set(int x){
	father[x] = 0;
}
int find(int x){
	if(father[x]<=0) return x;
	int fx=find(father[x]);
	father[x]=fx;
	return fx;
}
void Union(int x,int y){
	int fx=find(x),fy=find(y);
	if(fx==fy) return;
	if(father[fx]<father[fy]) father[fy]=fx;
	else{
		if(father[fx]==father[fy]) father[fy]--;
		father[fx]=fy;
	}
}
bool cmp(Edge& A, Edge& B) {
	return A.v < B.v;
} 

int main(){
	int n,m;
	int time;
	scanf("%d%d",&n,&m);
	for(int i = 1; i <= m; i++){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		edge[i].f = a; edge[i].t = b; edge[i].v = c;
	}
	sort(edge+1,edge+m+1,cmp);
	for(int i = 1; i <= n; i++) make_set(i); //初始化
	for(int i = 1; i <= m; i ++) {
		int f = edge[i].f, t = edge[i].t, v = edge[i].v;
		if(find(f) != find(t)) {
			Union(f, t);
			time = v;
		}
		if(find(1) == find(n)) break;
	}
	printf("%d",time);
	return 0;
}

四、数据结构设计I

题目

小唐正在学习数据结构。他尝试应用数据结构理论处理数据。最近,他接到一个任务,要求维护一个动态数据表,并支持如下操作:

1、插入操作(I):从表的一端插入一个整数。

2、删除操作(D):从表的另一端删除一个整数。

3、取反操作(R):把当前表中的所有整数都变成相反数。

4、取最大值操作(M):取当前表中的最大值。

如何高效实现这个动态数据结构呢?

输入格式:

第1行,包含1个整数M,代表操作的个数, 2≤M≤1000000。

第2到M+1行,每行包含1个操作。每个操作以一个字符开头,可以是I、D、R、M。如果是I操作,格式如下:I x, x代表插入的整数,-10000000≤x≤10000000。 。

输出格式:

若干行,每行1个整数,对应M操作的返回值。如果M和D操作时队列为空,忽略对应操作。

输入样例:

6
I 6
R
I 2
M
D
M

输出样例:

2
2

解题思路

根据题意显然需要一个队列来完成前两个操作,至于第三个操作,显然不能遍历一遍然后取负,这样的代价是巨大的,起初我也想到用一个标志(flag = 1表示所有数据乘以 -1;flag = 0所有乘 +1 即无变化)来完成这个操作,至于第四个操作显然还需要一个结构来辅助完成。
因为 map 自带一个排序的功能,而且也可以删除指定的数据,map 虽然不能存储两个相同的数据因为 map 可以表示映射的关系,所以可以存储对应数据的个数,这样一来操作就都解决了,在插入数据时,先对标志进行判断,来决定是插入相反数还是它本身,在取最大值时也一样,如果 flag = 1 则取最小值乘 -1 即为最大值,若 flag = 0 则取最大值输出即可。

参考代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
#include<map>

using namespace std;

queue<int> q;
map<int, int>  mp;
int flag = 0;   //flag为 1 时负;  为 0 时正。 

int main(){
	int M;
	char ch;
	int x;
	scanf("%d",&M);
	getchar();
	for(int i = 1; i <= M; i++){
		scanf("%c",&ch);
		getchar();
		switch(ch){
			case 'I' :
				scanf("%d",&x);
				getchar();
				
				if( flag == 0){   //找最大值 
					q.push(x);
		    		mp[x] ++;
		 		}
				else{           //找最小值 
					x = (-1)*x;
					q.push(x);
					mp[x] ++;
				}
				break;
			case 'D' :
				if(q.empty()) break;
				else {
					int t = q.front(); q.pop();
					mp[t] --;
					if(mp[t] == 0)	mp.erase(t);
				}
				break;
			case 'R' :
				if(flag)  flag = 0;
				else flag = 1;
				break;
			case 'M' :
				if(q.empty()) break;
				if(flag == 0){   //找最大值 
					int max = mp.rbegin()->first;
					printf("%d\n",max);
				}
				else{           //找最小值 
					int min = mp.begin()->first;
					printf("%d\n",min*(-1));
				}
				break;
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值