JLU数据结构荣誉课程第六次实验课

目录

 

7-1 高精度数加法 (100 分)

7-2 二叉树加权距离 (100 分) 

 7-3 修轻轨 (100 分)

 7-4 数据结构设计I (100 分)


7-1 高精度数加法 (100 分)

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

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

 

输入格式:

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

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

输出格式:

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

输入样例:

3
12345678910
12345678910
12345678910

输出样例:

37037036730 

思路:

高精度加法这道题目,我觉得要注意的地方就是在将字符数组转成整形后要逆序存放,每次读取一个字符串,要保证数组是干净的(因为空间限制,我们无法读入N个字符串,就开N个数组,实不相瞒,我一开始就是这样的,便于从低位到高位的进位,因为两数相加,得到的和可能要增加一位,如果正着存放从高位到低位,但是是从最低位进行计算,会在空间的运用处理上很别扭。

上代码,写的很丑,此处代码没有封装类,原谅我的能力,之后我会用类封装实现的。

#include<stdio.h>
#include<string.h>
int sum[20000];
int main(){
    int N,r1,r2;
    int a[105]={0};
    char A[105];
	scanf("%d",&N);	
	getchar();
	scanf("%s",A);
    r1=strlen(A);
	int j=1;
	for(int i=r1-1;i>=0;i--){
		sum[j]=(int)A[i]-'0';
		j++;
	}
	int ant=0,ent;  //进位 
	for(int k=2;k<=N;k++)
	{
		char s[105];
		scanf("%s",s);
	    r2=strlen(s);
		memset(a,0,sizeof(a));
		for(int i=r2-1,j=1;i>=0;i--,j++){
        	a[j]=(int)s[i]-'0';
    	}
    	if(r1<r2){
    		r1=r2;
		}
        ant=0;
    	for(int i=1;i<=r1;i++){
	    	ent=a[i]+sum[i]+ant;
	    	if(ent>9){
		    	ant=ent/10;
		    	sum[i]=ent%10;
	    	}
	    	else{
	    		ant=0;
		    	sum[i]=ent;
	    	}
    	}
        while(ant!=0){
    	   	r1++;
    	    sum[r1]=ant%10;
   		    ant=ant/10;
   	    }
    }
    for(int i=r1;i>=1;i--){
		printf("%d",sum[i]);
    }
	return 0;
	
} 

7-2 二叉树加权距离 (100 分) 

二叉树结点间的一种加权距离定义为:上行方向的变数×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

输出样例:

在这里给出相应的输出。例如: 

思路:

这道题,一开始没有明白题的意思,后来看了李同学的图解还有杨同学给我的讲解才明白了题的意思 。

这道题考察的是最近公共祖先。

最近公共祖先的寻找,应该是从u点开始一直向上直到找到根结点,并给每个被访问过的结点做标记,并记录每个结点高度,然后从v点开始向上查找,直到遇到第一个被访问过的结点,此结点即为最近公共祖先。

计算权值过程中,上行边数就是从u到最近公共祖先经历的边数,下行边就是最近公共祖先到u经历的边数。

代码如下:

#include<iostream>
using namespace std;
const int maxsize=100005;
int father[maxsize]={0};
int visited[maxsize]={0};
int main(){
	int N;
	int u,v;
	int distance=0;
	scanf("%d",&N);
	for(int i=1;i<=N-1;i++){//树n个节点有n-1条边 
	    int a,b;
		scanf("%d %d",&a,&b);
		father[b]=a;
	}
	scanf("%d %d",&u,&v);
	//根节点父亲为0 
	visited[u]=1;
    int ceng=1;
	while(father[u]){  //上行 
		visited[u]=ceng++;//被访问标记 ,顺便记录高度
        u=father[u];
	}
    visited[1]=ceng;
	while(!visited[v]){ //下行,逆着上去找最近公共祖先
        distance+=2;
		v=father[v];
	}
	printf("%d\n",distance+visited[v]*3-3);//最近公共祖先可不一定是根
	return 0;
}

 7-3 修轻轨 (100 分)

长春市有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是否连通,如果连通,跳出循环 。

代码如下:

#include<iostream>
#include<algorithm>
#define maxsize 200005
using namespace std;
struct TE{  //最小支撑树边的集合 
	int head;//边的始点 
	int tail;//边的终点 
	int cost;//边的开销 
}T[maxsize];
int father[maxsize];
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(struct TE a,struct TE b){
	return a.cost<b.cost;
}
int Kruskal_method(int m,int n){
	int i,j,v,u;
	int result;
	sort(T+1,T+m+1,cmp);
	for(i=1;i<=m;i++) make_set(i);
	for(i=1,j=1;i<=m;i++){
		int u=T[i].head,v=T[i].tail;
		if(find(u)!=find(v)){
			Union(u,v);
			result=T[i].cost;
		}
		if(find(1)==find(n))return result;
	}
}
int main(){
	int n,m;
	scanf("%d %d",&n,&m);
	if(n==1){
		printf("0\n");
		return 0;
	}
	for(int i=1;i<=m;i++){
		scanf("%d %d %d",&T[i].head,&T[i].tail,&T[i].cost);
	}
	int result=Kruskal_method(m,n);
	printf("%d\n",result);
	return 0;
}

 7-4 数据结构设计I (100 分)

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

  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

 思路:

这道题我写了很长时间,用了最暴力的方法,寻找最大值和把所有数反转成相反数都是暴力方法,我想过设标记进行状态反转,但是由于太菜,没有想到接下来应该怎么办。

暴力代码如下:(他过了两个测试点,得了四十分)

#include<bits/stdc++.h>
#include<deque>
using namespace std;
deque <int> Q1,Q2; 
const int INF=1e8+5;
int maxn=-INF;
int judge=1;
void Oper(char x){
	
    switch(x){
    	case 'I':{
    		int y;
	    	scanf("%d",&y);
	    	if(y>maxn)maxn=y;
	    	if(judge==1)
	    	{
		    	Q1.push_front(y);
		    	Q2.push_front(-y);
		    }
		    else
		    {
		    	Q1.push_front(-y);
		    	Q2.push_front(y);
			}
			break;
			}
		case 'D':{
			if(!Q1.empty()){
				if(judge==1)
				{
			        deque <int> ::iterator it;
			        it=Q1.end()-1;
			        if((*it)==maxn)
			        {
			        	Q1.pop_back();
			        	Q2.pop_back();
			        	maxn=-INF;
			        	for(it=Q1.begin();it!=Q1.end();it++){
			    	    	if(maxn<(*it)){
			    		        maxn=(*it);
						    }
					    }
				    }
				}
				else
			    {
				    deque <int> ::iterator it;
			        it=Q2.end()-1;
			        if((*it)==maxn)
			        {
			        	Q1.pop_back();
			        	Q2.pop_back();
			        	maxn=-INF;
			        	for(it=Q2.begin();it!=Q2.end();it++){
			    	    	if(maxn<(*it)){
			    		        maxn=(*it);
						    }
					    }
				    }	
				}
			}
			else
			{
				maxn=-INF;
				judge=1;
			}
		    break;
		}
		case 'R':{
			if(!Q1.empty()){
				if(judge==1){
				    judge=0;
				    deque <int> ::iterator it;
			       	maxn=-INF;
			       	for(it=Q2.begin();it!=Q2.end();it++){
			   	    	if(maxn<(*it)){
			   		        maxn=(*it);
					    }
				    }
			    }
			    else{
			    	judge=1;
			    	deque <int> ::iterator it;
			       	maxn=-INF;
			       	for(it=Q1.begin();it!=Q1.end();it++){
			   	    	if(maxn<(*it)){
			   		        maxn=(*it);
					    }
				    }
			    }
	       } 
	       else{
		    	maxn=-INF;
		    	judge=1;
	    	}
	 //   	printf("*%d*",maxn);
			break;
		}
		case 'M':{
			if(!Q1.empty()){
		    	printf("%d\n",maxn);
		    }
		    else{
		    	maxn=-INF;
		    	judge=1;
			}
			break;
		}
	}
	
}
int main(){
	int M;
	char a[2];
	scanf("%d",&M);
	getchar();
	for(int i=1;i<=M;i++){
		scanf("%s",a);
		Oper(a[0]);
	}
	return 0;
}

 改正的方法是受到大佬们的解题报告的指导,

用单调队列获得最大值,设置一个judge来表示状态。维护两个单调队列,一个单调递增,一个单调递减,例如:当judge=1,将读入的数直接入队,当judge=0,将读入的数的相反数入队,访问时,当状态为1时,输出单调递减队列的队头;当状态为0,输出单调递增队列的队头的相反数。

要记住的一点是:一定要记得队列的先进先出性质,如果要删除的元素比最大值小,证明维护的单调队列里面是没有这个数的,无需考虑单调队列里面对他的删除操作。 

代码如下:

#include<iostream>
#include<queue>
#include<deque>
using namespace std;
int judge=1;
queue<int> Q;
deque<pair<int,int> > M1,M2;
void Oper(char x){
    switch(x){
    	case 'I':{
    		int y;
	    	scanf("%d",&y);
	   // 	printf("<<<<<11111\n");
	        if(judge==0){
	        	y=y*(-1);
			}
		    Q.push(y);
		   	//维护递减序列 
		//   	printf("<<<<<<32222\n");
		   	while(!M1.empty()&&y>M1.back().first){
				M1.pop_back();
			}
		   	if(M1.empty()||y<M1.back().first){
		    	M1.push_back(make_pair(y,1));
			}
			else
			{
				if(y==M1.back().first)
				{
					M1.back().second++;
				}
			}
			//维护递增序列 
		    while(!M2.empty()&&y<M2.back().first){
				M2.pop_back();
			}
		   	if(M2.empty()||y>M2.back().first){
		    	M2.push_back(make_pair(y,1));
			}
			else
			{
				if(y==M2.back().first)
				{
					M2.back().second++;
				}
			}
			break;	
		}
		case 'D':{
	        if(!Q.empty()){
	        	int y=Q.front();
	        	Q.pop();
	        	//在此,一定要记得队列的先进先出性质,如果y比最大值小,证明单调队列里面是没有y的 
	        	if(y==M1.front().first){
	        		M1.front().second--;
	        		if(M1.front().second==0){
	        			M1.pop_front();
					}
				}
				if(y==M2.front().first){
	        		M2.front().second--;
	        		if(M2.front().second==0){
	        			M2.pop_front();
					}
				}	
			}
			else{
				break;
			} 
		    break;
		}
		case 'R':{
	        if(judge==1){
	        	judge=0;
			}
			else{
				judge=1;
			}
			break;
		}
		case 'M':{
			if(Q.empty())
			{
				break;
			}
			else
			{
		    	if(judge==1){
			    	printf("%d\n",M1.front().first);
			    }
			    else{
				    printf("%d\n",-(M2.front().first));
			    }
			}
			break;
		}
	}
	
}
int main(){
	int M;
	char a[2];
	scanf("%d",&M);
	getchar();
	for(int i=1;i<=M;i++){
		scanf("%s",a);
		Oper(a[0]);
	}
	return 0;
}

 

 

 

 

  

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值