这是第一次综合上机实验
一、高精度加法
题目
高精度数是指大大超出了标准数据类型能表示的范围的数,例如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;
}