您的位置:首页 > 其它

算法第二次上机部分题解

2017-10-30 18:20 148 查看

总结

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

关于题目难度,我感觉对于我这种做题仅限于北航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;//因为起始tmp为n,所以减一
        }
        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。

       思路是先将两数组排序,一个升序,一个降序,然后取两个指针i和j指向数组当前元素,若当前元素和小于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)//小于t时i前移
            {
                i++;
            }
            Else//大于t时j前移
            {
                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("");
    }
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: