2020级数据结构荣誉课第六次上机考试题解(综合)

题目一:7-1 高精度数加法 (100 分)

指导教师:谷方明                   单位:吉林大学软件学院

目录

题目一:7-1 高精度数加法 (100 分)

输入格式:

输出格式:

输入样例:

输出样例:

 题目分析:

代码:

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

输入格式:

输出格式:

输入样例:

输出样例:

题目分析:

 代码:

题目三:7-3 修轻轨 (100 分)

输入格式:

输出格式:

输入样例:

输出样例:

题目分析: 

代码:

题目四:7-4 数据结构设计I (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 分)

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

  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

题目分析: 

方法: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}^{-1}} ~ ^{_{F}^{-1}}F(x) = x  即让对个x 有^{_{F}^{-1}}F(x) = x,即可完成双向记录。实际上不用每次都对每个x进行^{_{F}^{-1}}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,不如单调队列的时空复杂度好。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值