算法第二次上机部分题解

总结

本次上机主要考察知识点为快排、二分查找、优先队列、数组线性操作等算法知识。

关于题目难度,我感觉对于我这种做题仅限于北航oj的小菜比来说略大。上机前给出了知识点,但实际落实到题目中时,我却根本看不出题目在考察哪个知识点(或许我只能做做裸题吧),比如,上机时听说第一题是二分,却总是找不到分什么。B题考察优先队列和数组操作(其实看穿本质,用公式也能过),C题利用前缀和结合STL中的map(用数组会MLE),D题考察数组操作和优先队列(当然,我用set集合也能达到同样效果),E题不谈,听大佬说不适合我这种渣渣做,G题直接模拟题目的步骤就行(感谢助教送的100分)。

总地来说,我还是见识太少,练习不够,缺少那种短时间迅速思考求解题目的能力。因此针对以上从上机中发现的不足,我有必要勤于思考,多加练习,争取一眼看穿问题的本质,而不是拿到问题无从下手。

A 画个圈圈诅咒你

思路分析

此题考察二分查找。

       输入时将每个圈的左右顶点放入两个数组l和r,先对两个数组排序,对于每个圈,分别计算它左边不相交的圈和右边不相交的圈。对于左边,lower_bound(r,r+n,l[i])-r即为所求圈数;对于右边,先求出与它相交的圈数pos1,然后用总圈数-pos1-1即为所求圈数。最后将所有圈数相加,但要注意此时重复求了每个圈的不相交数,因此要除以2。

算法分析

       二分查找的时间复杂度为log(n),外面有层循环,所以算法的时间复杂度为nlog(n)。

       关于lower_bound和upper_bound:这是STL提供的两个二分查找函数,lower_bound返回第一个>=给定元素的元素位置,要求元素从小到大排列;返回第一个>给定元素的元素位,要求元素从小到大排列。

       关于STL中查找函数的具体描述:http://blog.csdn.net/zhongkeli/article/details/6883288

参考代码

LL ans,x,R;

LL l[maxn],r[maxn];

intmain()

{

    intn;

    while(~scanf("%d",&n))

    {

        for(int i =0;i < n;i++) ~scanf("%lld%lld",&x,&R),l[i] =x-R,r[i] = x+R;

        sort(l,l+n);sort(r,r+n);

        ans = 0;

        for(int i =0;i < n;i++)

        {

            int tmp = n-1,pos_1,pos_2;

            pos_1 = upper_bound(l,l+n,r[i])-l;

            if(pos_1&&l[pos_1-1] <= r[i]) pos_1--;

            tmp -= pos_1;//当前圈右边与它分开的圈数

            pos_2 = lower_bound(r,r+n,l[i])-r; //当前圈左边与它分开的圈数

            tmp += pos_2;

            ans += tmp;//为起始tmpn,所以减一

        }

        printf("%lld\n",ans/2);//每个答案都重复数了一次,所以要除以二

    }

}

B Bamboo的OS实验

思路分析

此题分两种情况考虑。

第一种,相同编号指令间的最短间隔时间很小,以至于完成任务不需要思考人生的时间。这种情况下,ans为x,即总指令个数。

若不满足上述情况,则考虑一个公式:ans = (max_x-1)*(t+1) + max_cnt其中x为总指令个数,max_x为数量最多的那些指令的指令个数,t为相同编号指令间的最短间隔时间,max_cnt为数量最多的那些指令的编号总数。

以下给出证明:

以样例为例,将总时间分为两部分max_x-1次完整的分配和最后剩下的max_cnt,即

1-->2-->思考人生-->1-->2-->思考人生-->1-->2,蓝色为第一部分,红色为第二部分,此时,max_x = 3,t = 2,max_cnt = 2,因此ans = (3-1)*(2+1)+2 = 8。

最后,综合以上两总情况,给出最后的公式:ans = max(x,(max_x-1)*(t+1) + max_cnt)

算法分析

       由于推导出了公式,因此,时间复杂度为O(n)。

参考代码

inta[31];

 

intmain()

{

    intx,y ;

    while(~scanf("%d",&x))

    {

        memset(a,0,sizeof(a));

        for(int i =0;i < x;i++)

        {

            scanf("%d",&y);

            a[y]++;//记录各指令出现的次数

        }

        int max_x =0,max_cnt =0;

        for(int i =1;i <=30;i++)

        {

            if(a[i] > max_x)

            {

                max_cnt = 0;//出现新的最大值,种数归零

                max_x = a[i];//更新最大指令数

            }

            if(a[i] == max_x)

            {

                max_cnt++;//最大编号种数加一

            }

        }

        int t;

        scanf("%d",&t);

        intans = max(x,(max_x-1)*(t+1) + max_cnt);//核心公式

        printf("%d\n",ans);

    }

}

C AlvinZH的儿时梦想——坦克篇

思路分析

       这题可以看作一道数组线性操作题,但是真的用数组却又会MLE,因此我选用map来避免。

       此题的核心是前缀和,mp[tmp_sum]记录每行达到tmp_sum宽度时缝隙的个数。输入结束后,遍历map,找到最大缝隙数max_sum,用n-max_sum即为答案。

算法分析

       因为核心操作在输入时已经完成,因此时间复杂度为O(mn)。

       关于一维前缀和: 这个优化主要是用来在O(1)时间内求出一个序列a中,a[i]+a[i+1]+……+a[j]的和。具体原理十分简单:用sum[i]表示(a[1]+a[2]+……+a[i]),其中sum[0]=0,则(a[i]+a[i+1]+……+a[j])即等于sum[j]-sum[i-1]。

       主要应用于降维

参考资料:http://blog.csdn.net/K_rew/article/details/50527287

参考代码

map<int,int>mp;

intmain()

{

    intn,m;

    inttmp,tmp_sum;

    while(~scanf("%d%d",&n,&m))

    {

        mp.clear();

        for(int i =0; i < n; i++)

        {

            tmp_sum = 0;

            for(int j =0; j < m; j++)

            {

                scanf("%d",&tmp);

                if(j == m-1)break;//达到总宽度时的缝隙无法通过

                tmp_sum += tmp;//前缀和

                mp[tmp_sum]++;//此宽度的缝隙数加一

            }

        }

        if(m ==1) printf("%d\n",n);//因为前面达到总宽度时未计算前缀和,因此m=1单独讨论

        else

        {

            int max_sum =0;//记录同一宽度下缝隙数量的最大值

            for(map<int,int>::iterator it = mp.begin(); it != mp.end(); it++)

            {

                max_sum =max(max_sum,(*it).second);

            }

            printf("%d\n",n-max_sum);//n-缝隙数即为撞的建筑物数

        }

    }

}

D Bamboo的饼干

思路分析

       此题类似于“四和归零”,但不能照搬套路,否则会MLE。

       思路是先将两数组排序,一个升序,一个降序,然后取两个指针ij指向数组当前元素,若当前元素和小于t,i向前移动(使得元素和增大);若当前元素和大于t,j向前移动(使得元素和增大),每当元素和等于t时,将这对元素当作pair放入集合(set)S中,利用set元素的唯一性自动去重,同时利用set元素有序性自动排序,可谓一举两得,实在美滋滋。最后判断S是否为空,是则输出“OTZ”,否则按顺序将set中的元素一一打印出来。不过要记得每组数据最后要输出换行,不然会PE哦~

算法分析

       本算法的核心在于两指针i和j的移动,因此时间复杂度为O(n)。

参考代码

LL a[maxn],b[maxn];

boolcmp(const LL &a,const LL &b)

{

    returna>b;

}

intmain()

{

    intn;

    set<pair<LL,LL>>S;

    while(~scanf("%d",&n))

    {

        for(int i =0; i < n; i++)

        {

            scanf("%lld",&a[i]);

        }

        for(int i =0; i < n; i++)

        {

            scanf("%lld",&b[i]);

        }

        LL t;

        scanf("%lld",&t);

        sort(a,a+n);//升序排列

        sort(b,b+n,cmp);//自定义cmp,降序排列

        int i =0,j =0;

        bool fla =true;

        while(i < n&&j < n)

        {

            if(a[i] + b[j] == t)//满足条件的元素对放入set

            {

                pair<LL,LL>p;

                p.first = a[i];

                p.second = b[j];

                S.insert(p);

                i++;j++;

            }

            elseif(a[i] + b[j] < t)//小于ti前移

            {

                i++;

            }

            Else//大于tj前移

            {

                j++;

            }

        }

        if(S.empty())

        {

            printf("OTZ\n");

        }

        else

        {

            for(set<pair<LL,LL> >::iterator it =S.begin(); it != S.end(); it++)

            {

                pair<LL,LL>p;

                p = *it;

                printf("%lld %lld\n",p.first,p.second);

            }

        }

        while(!S.empty())//清空set

        {

            S.clear();

        }

        puts("");//记得最后输出换行

    }

}

G ModricWang's Real QuickSort

思路分析

       和上次上机相同,这次的真快排同样只需模拟题目所给出的步骤就行。

算法分析

       因为只需进行两次递归,因此时间复杂度为O(n)。

参考代码

#include<cstdio>

#define maxn 1000007

inta[maxn];

 

voidmov(int &a,int &b)

{

    inthold = a;

    a = b;

    b = hold;

}

 

intmain()

{

    intn;

    while(~scanf("%d",&n))

    {

        for(int i =0; i < n; i++)

        {

            scanf("%d",&a[i]);

        }

 

        int i =0;

        int j = n-1;

        int mid = n/2;

        int key = a[mid];

        while(i <= j)//第一遍递归

        {

            while(a[i] < key)

            {

                i++;

            }

            while(a[j] > key)

            {

                j--;

            }

            if(i <= j)

            {

                mov(a[i],a[j]);

                i++;

                j--;

            }

        }

 

        int tem = i;

 

        i = 0;

        j = tem - 1;

        mid = tem/2;

        key = a[mid];

        while(i <= j)//第二遍递归

        {

            while(a[i] < key)

            {

                i++;

            }

            while(a[j] > key)

            {

                j--;

            }

            if(i <= j)

            {

                mov(a[i],a[j]);

                i++;

                j--;

            }

        }

 

        for(int k = i;k < tem;k++)//取出第二部分的元素

        {

            printf("%d ",a[k]);

        }

        puts("");

    }

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值