题目一: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
题目分析:
可以封装一个类,来对各种大数进行高精度模拟。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1001;
class bignum
{
int digit;
bool f;
int n[maxn];
void invert();
public:
bignum();
friend istream& operator >>(istream& in,bignum& a);
friend ostream& operator <<(ostream& out,bignum &a);
friend bignum operator + (const bignum a,const bignum b);
};
ostream& operator <<(ostream& out,bignum& a)
{
for (int i = 1;i<=a.digit;i++)
{
out<<a.n[i];
}
if (a.digit == 0)
out<<"0";
return out;
}
istream& operator >>(istream& in,bignum& a)
{
char s[maxn];
in>>s;
a.digit = strlen(s);
int i = 0;int j = 1;
if (s[0] == '-')
{
a.f = 1;i++;a.digit--;
}
while (s[i] == '0')
{
i++;
}
for (;i<a.digit;i++)
{
a.n[j++] = s[i] - '0';
}
a.digit = j-1;
return in;
}
bignum::bignum()
{
for (int i = 0;i<=maxn;i++)
n[i] = 0;
digit = 0;
f = 0;
}
void bignum::invert()
{
for (int i = 1;i<=digit/2;i++)
swap(n[i],n[digit-i+1]);
}
bignum operator + (const bignum a,const bignum b)
{
bignum c,a1,b1;
a1 = a;b1 = b;
int t = 0;
int len = max(a1.digit,b1.digit);
a1.invert();b1.invert();
for (int i = 1;i<=len;i++)
{
c.n[i] = (a1.n[i] + b1.n[i] + t)%10;
t = (a1.n[i] + b1.n[i] + t)/10;
}
c.digit = len;
if (t)
{
c.digit++;
c.n[c.digit] = t;
}
c.invert();
return c;
}
int main()
{
int n;
cin>>n;
bignum a,b;
cin>>a;
if (n >= 2)
{
cin>>b;
a = a+b;
}
for (int i = 1;i<=n-2;i++)
{
bignum c;cin>>c;
a = a+c;
}
cout<<a;
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
输出样例:
在这里给出相应的输出。例如:
8
题目分析:
数据结构选择上,此题可以用静态链表来表示二叉树。
此题加权距离定义为上行边数*3+ 下行边数*2,所以可以分为两种情况:
1.两个点中一个点为另外一个点的祖先,加权距离无上行边数*3,只有下行边数*2;
2.两个点有非自身的公共祖先,加权距离正常计算;
另外,可以记录每个节点的层数,从而快速的算不同层的祖先孩子之间的距离。
情况1的函数:
void dfs(int u,int v) //U高 V低
{
if (layer[u] == layer[v])
return;
if (layer[u]+1 == layer[v])
{
if (l[u] == v||r[u] == v)
{
flag = 1;return;
}
}
if (l[u])
dfs(l[u],v);
if (r[u])
dfs(r[u],v);
}
情况2的函数:
int lca(int u,int v)
{
if (u == v)
return u;
else
lca(p[u],p[v]);
}
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100001;
int l[maxn];
int r[maxn];
int p[maxn];
int mark[maxn];
int layer[maxn];
int flag = 0;
void dfs(int u,int v) //U高 V低
{
if (layer[u] == layer[v])
return;
if (layer[u]+1 == layer[v])
{
if (l[u] == v||r[u] == v)
{
flag = 1;return;
}
}
if (l[u])
dfs(l[u],v);
if (r[u])
dfs(r[u],v);
}
int lca(int u,int v)
{
if (u == v)
return u;
else
lca(p[u],p[v]);
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int n;
cin>>n;
for (int i = 1;i<=n;i++)
{
layer[i] = mark[i] = 0;
l[i] = r[i] = 0;
}
int t1,t2;
while (1)
{
cin>>t1>>t2;
if (mark[t2])
{
break;
}
mark[t2] = 1;
layer[t2] = layer[t1]+1;
if (l[t1]&&r[t1])
break;
if (!l[t1])
{
l[t1] = t2;
p[t2] = t1;
}
else if (!r[t1])
{
r[t1] = t2;
p[t2] = t1;
}
}
int u = t1,v = t2;
if (layer[u]>layer[v]) //u矮
{
dfs(v,u);
}
else
{
dfs(u,v);
}
if (flag)
{
cout<<2*abs(layer[u]-layer[v]);
}
else
{
int LCA = lca(u,v);
cout<<3*layer[u] + 2*layer[v] - 5*layer[LCA];
}
// while (1);
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
题目分析:
此题为图论题,数据结构准备方面,应该选用点图或边图。不难发现,此题用Kruscal算法较为方便,所以应该用边图来建图。
在并查集检验联通时,只需验证 1 与 n 是否联通即可。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2000005;
struct Edge //边图
{
int u;
int v;
int cost;
bool friend operator <(Edge a,Edge b)
{
return a.cost<b.cost;
}
}edge[maxn];
int father[100001];
int n;
void make_set()
{
for (int i = 1;i<=n;i++)
{
father[i] = 0;
}
}
int FIND(int i) //路径压缩
{
if (father[i]<=0) return i;
else return father[i] = FIND(father[i]);
}
void UNION(int x,int y) //按秩合并
{
int fx = FIND(x);
int 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;
}
}
int m;
int ans;
int main(void)
{
cin>>n>>m;
make_set();
for(int i=0;i<m;i++)
{
cin>>edge[i].u>>edge[i].v>>edge[i].cost;
}
sort(edge,edge+m);
for(int i=0;i<m;i++)
{
int u=edge[i].u;
int v=edge[i].v;
int mini=edge[i].cost;
if(FIND(u)==FIND(v)) continue;
UNION(u,v);
if(FIND(1)==FIND(n)) //停止条件
{
ans=mini;
break;
}
}
cout<<ans;
return 0;
}
题目四:7-4 数据结构设计I (100 分)
小唐正在学习数据结构。他尝试应用数据结构理论处理数据。最近,他接到一个任务,要求维护一个动态数据表,并支持如下操作:
-
插入操作(I):从表的一端插入一个整数。
-
删除操作(D):从表的另一端删除一个整数。
-
取反操作(R):把当前表中的所有整数都变成相反数。
-
取最大值操作(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
题目分析:
方法:multiset/大小根堆
multiset空间复杂度较高,但实现较为简单。这里介绍一下大小根堆的做法。
难点主要在与
1.插入元素取原值或相反数要根据R的次数来定(大大降低时间复杂度,从O(N)降低至O(1))
2.堆中保存的是队列的下标,所以取最大值最小值简单,入队也比较简单,但是出队比较难,因为需要知道队列中各个元素在堆中的位置,所以需要在开一个数组来反标记,实现双向记录
所以这种操作在查找的时间复杂度为0(1),整体时间复杂度为O(N*logN),效率比multiset高。
数组可以理解为一种映射。
在映射关系 x~F(x) 中,设 x 为堆下标,F(x)为队列下标,因为堆管理着队列的下标。此时堆对于队列的查找为O(1),队列对于堆的查找为O(N)(一次遍历)
可以在建立一个映射关系: ~
F(x) = x 即让对个x 有
F(x) = x,即可完成双向记录。实际上不用每次都对每个x进行
F(x) = x,只需要对堆中有元素变动的操作即可。
堆的改进如下:(只展示大根堆了)
inline void up_big(int x) //大根堆上浮
{
int i = x ;
if (i == 1) big[h_big[i]] = i;
while( i>1&& q[h_big[i]]>q[h_big[i/2]])
{
swap(h_big[i],h_big[i/2]);
big[h_big[i]] = i; //改进
big[h_big[i/2]] = i/2; //改进
i/= 2;
}
}
inline void down_big(int x) //大根堆下沉
{
int i = x,y;
while (2*i<=hlen_big)
{
y = 2*i;
if (2*i+1<=hlen_big&&q[h_big[ 2*i+1 ]]>q[h_big[ 2*i ]] ) ++y;
if (q[h_big[y]]>q[h_big[i]])
{
swap(h_big[i],h_big[y]);
big[h_big[i]] = i; //改进
big[h_big[y]] = y; //改进
i = y;
}
else break;
}
}
inline void insert_big(int x) //大 插入
{
++hlen_big;
h_big[hlen_big] = x;
big[x] = hlen_big; //改进
up_big(hlen_big);
}
inline void delete_big( int x ) //大 删除
{
int t = h_big[x];
h_big[x] = h_big[hlen_big];
big[h_big[x]] = x; //改进
--hlen_big;
// cout<<q[t];
if( q[h_big[x]] > q[t] ) up_big(x); else down_big(x);
}
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000005;
int q[maxn],r,f;
int hlen_big = 0,hlen_small;
int h_big[maxn],h_small[maxn];
int big[maxn],small[maxn];
inline void up_big(int x) //大根堆上浮
{
int i = x ;
if (i == 1) big[h_big[i]] = i;
while( i>1&& q[h_big[i]]>q[h_big[i/2]])
{
swap(h_big[i],h_big[i/2]);
big[h_big[i]] = i;
big[h_big[i/2]] = i/2;
i/= 2;
}
}
inline void down_big(int x) //大根堆下沉
{
int i = x,y;
while (2*i<=hlen_big)
{
y = 2*i;
if (2*i+1<=hlen_big&&q[h_big[ 2*i+1 ]]>q[h_big[ 2*i ]] ) ++y;
if (q[h_big[y]]>q[h_big[i]])
{
swap(h_big[i],h_big[y]);
big[h_big[i]] = i;
big[h_big[y]] = y;
i = y;
}
else break;
}
}
inline void insert_big(int x) //大 插入
{
++hlen_big;
h_big[hlen_big] = x;
big[x] = hlen_big;
up_big(hlen_big);
}
inline void delete_big( int x ) //大 删除
{
int t = h_big[x];
h_big[x] = h_big[hlen_big];
big[h_big[x]] = x;
--hlen_big;
// cout<<q[t];
if( q[h_big[x]] > q[t] ) up_big(x); else down_big(x);
}
inline void up_small(int x) //小根堆上浮
{
int i = x ;
while( i>1&& q[h_small[i]]<q[h_small[i/2]])
{
swap(h_small[i],h_small[i/2]);
small[h_small[i]] = i;
small[h_small[i/2]] = i/2;
i/= 2;
}
}
inline void down_small(int x) //小根堆下沉
{
int i = x,y;
while (2*i<=hlen_small)
{
y = 2*i;
if (2*i+1<=hlen_small&&q[h_small[ 2*i+1 ]]<q[h_small[ 2*i ]] ) y++;
if (q[h_small[y]]<q[h_small[i]])
{
swap(h_small[i],h_small[y]);
small[h_small[i]] = i;
small[h_small[y]] = y;
i = y;
}
else break;
}
}
inline void insert_small(int x) //小 插入
{
hlen_small++;
h_small[hlen_small] = x;
small[x] = hlen_small;
up_small(hlen_small);
}
inline void delete_small( int x ) //小 删除
{
int t = h_small[x];
h_small[x] = h_small[hlen_small];
small[h_small[x]] = x;
hlen_small--;
// cout<<q[t];
if( q[h_small[x]] < q[t] ) up_small(x); else down_small(x);
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
char s;
int k;int flag = 1;
cin>>k;
r = f = 0;hlen_big = 0,hlen_small = 0;flag = 1;
for (register int j = 1;j<=k;j++)
{
cin>>s;
if (s == 'I')
{
cin>>q[f];
if (flag == 0) q[f]= -q[f];
insert_big(f);
insert_small(f);
f++;
}
else if (s == 'M')
{
if (r == f)
continue;
if (flag == 1)
cout<<q[h_big[1]]<<'\n';
else
cout<<-q[h_small[1]]<<'\n';
}
else if (s == 'D')
{
if (r == f) continue;
delete_big(big[r]);
delete_small(small[r]);
r++;
}
else if (s == 'R')
{
flag = !flag;
}
}
}
但是空间复杂度接近10MB,不如单调队列的时空复杂度好。