20级课程作业-贪心算法 做题总结(一)

A
题意:有N头牛,分别在不同位置,每两头牛可以互相叫,音量必须等于两牛之间的距离,即可以进行N*(N-1)次对话,求所有牛产生音量的总和。

乍一看这题挺简单,先把位置按从小到大排序,然后在每个位置分别减其他位置求和。但需要两个for循环嵌套,试一试果然超时。又换了好几种方法,发现自己想太多,做了好久终于过了,总结一下,需要用到排序加规律。以下是解题回顾。(正解在最后)

两个for循环嵌套,结果Compile Error

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;

int main()
{
    
    
    long long int n,i,j,a[10005],sum=0;
    cin>>n;
    for(i=0; i<n; i++)
        cin>>a[i];
    sort(a,a+n);
    for(i=0; i<n; i++)
    {
    
    
        for(j=0; j<n; j++)
        {
    
    
            sum+=abs(a[j]-a[i]);
        }
    }
    cout<<sum;
    return 0;
}

在这里插入图片描述
错因:系统分不清该是哪种类型的数据
解决办法:在abs括号里面加强制转换类型。

改正后结果时间限制


#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
int main()
{
    
    
    long long int n,i,j,a[10005],sum=0;
    cin>>n;
    for(i=0; i<n; i++)
        cin>>a[i];
    sort(a,a+n);
    for(i=0; i<n; i++)
    {
    
    
        for(j=0; j<n; j++)
        {
    
    
            sum+=abs((long)(a[j]-a[i]));
        }
    }
    cout<<sum;
    return 0;
}

换方法,先排序再寻找规律
以1 3 6 10 15 21为例
在这里插入图片描述
发现每个数之间的距离出现的次数存在规律,注意奇数偶数的区别

#include<iostream>
#include<algorithm>
using namespace std;

int main()
{
    
    
    long long int n,i,j,a[10005],b[10005],sum=0,p=0,d=0,c;
    cin>>n;
    for(i=0; i<n; i++)
        cin>>b[i];
    sort(b,b+n);
    if(n%2==0)
    {
    
    
        p=n/2;
        j=p;
        c=n;
        for(i=p; i>=0; i--)
        {
    
    
            a[i-1]=p*c;
            p++;
            c-=2;
        }
        for(i=n-2; i>j-1; i--)
        {
    
    
            a[i]=a[0+d];
            d++;
        }
    }
    else {
    
    
        p=n/2;
        j=p;
        c=n-1;
        for(i=p; i>=0; i--)
        {
    
    
            a[i-1]=(p+1)*c;
            p++;
            c-=2;
        }
        for(i=n-2; i>j-2; i--)
        {
    
    
            a[i]=a[0+d];
            d++;
        }
    }
    for(i=0; i<n-1; i++)
    {
    
    
        sum+=a[i]*(b[i+1]-b[i]);
    }
    cout<<sum;
    
    return 0;
}

这样做虽然能过题但是太复杂了,继续优化。
如果只考虑一个方向,最后再乘以2
在这里插入图片描述

这样规律就比较明显了,每个间隔被加的次数等于前面元素个数加总数减前面元素个数,最后用间隔乘出现的次数求和即可。

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;

int main()
{
    
    
    long long int n,i,j,a[10005],sum=0;
    cin>>n;
    for(i=0; i<n; i++)
        cin>>a[i];
    sort(a,a+n);
    for(i=1; i<n; i++)
    {
    
    
       sum+=(a[i]-a[i-1])*i*(n-i)*2;
    }
    cout<<sum;
    return 0;
}

B
题意:给你N对数,每对数第一个代表物品利润,第二个代表物品能卖的时间期限,每天只能卖一件物品,求最大利润。

一开始题意理解错了,以为时间期限指的是当天卖。
实际上是比如期限为3,指1-3天内都能卖这个物品,如果给你4对数[30,3],[20,3],[10,2],[5,1],则你在第三天卖价值为30的,第二天卖价值为20的,第一天卖价值为10的利润最大。
所以题干弄明白了开始排序,按照价值从小到大排序,先选价值最大的,然后把那一天标记,其他和被标记的这一天时间期限相同的就减去一天(因为时间是按照从后往前排的),然后继续查找。注意,假设如果第三天已被标记了,而以后的数的时间期限有2个以上是4天,也就是说,遇到第一个时间期限4,就把第四天标记,再将之后的4都减1变为3,因为第三天已经标记了,所以就需要继续减1,直到那一天大于等于第一天且没被标记。
思路理清就可以写代码了,注意多组输入用while(cin>>n)。

#include<iostream>
#include<algorithm>
using namespace std;
struct lr
{
    
    
    int p,d;
} num[10005];
bool cmp(lr a,lr b)
{
    
    
    if(a.p>b.p)
        return true;
    else if(a.p==b.p)
        if(a.d>b.d)
            return true;
    return false;
}
int main()
{
    
    
    int n;
    while(cin>>n)
    {
    
       long long int i,j,sum=0,a[10005]= {
    
    0};
        for(i=0; i<n; i++)
            cin>>num[i].p>>num[i].d;
        sort(num,num+n,cmp);
        for(i=0; i<n; i++)
        {
    
    
            while(a[num[i].d]==1)
            {
    
    
                num[i].d--;
            }
            if(num[i].d==0)
                continue;
            if(a[num[i].d]==0)
            {
    
    
                sum+=num[i].p;
                a[num[i].d]=1;
            }
            for(j=i+1; j<n; j++)
            {
    
    
                if(num[j].d==num[i].d)
                    num[j].d--;
            }
        }
        cout<<sum<<endl;
    }
    return 0;
}

另一种写法

#include<iostream>
#include<algorithm>
using namespace std;
struct n
{
    
    
    int p,d;
    bool operator <(const n &b) const
    {
    
    
        return p>b.p;
    }
}num[10005];
int main()
{
    
    
    int n;
    while(cin>>n)
    {
    
       long long int i,j,sum=0,a[10005]= {
    
    0};
        for(i=0; i<n; i++)
            cin>>num[i].p>>num[i].d;
        sort(num,num+n);
        for(i=0; i<n; i++)
        {
    
    
            while(a[num[i].d]==1)
            {
    
    
                num[i].d--;
            }
            if(num[i].d==0)
                continue;
            if(a[num[i].d]==0)
            {
    
    
                sum+=num[i].p;
                a[num[i].d]=1;
            }
            for(j=i+1; j<n; j++)
            {
    
    
                if(num[j].d==num[i].d)
                    num[j].d--;
            }
        }
        cout<<sum<<endl;
    }
    return 0;
}

D
题意:给你N个坑,长度为L的木板,每个坑左右端点已知,问最少多少个板能把坑补上

一开始想的比较复杂比较绕,这里不在叙述,又换了一种思路,依然是指针移动问题,可以把每个坑看成一个区间,先对其排序,标记第一个坑左端点的位置,然后铺板,标记随着板移动,当第一个坑填完后,如果标记的位置没到下一个坑,就从下一个坑的左端标记;如果标记的位置超过了下一个坑的左端,则在此基础上继续铺板

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
struct xd
{
    
    
    int z,y;
    bool operator<(const xd &b)const
    {
    
    
        return z<b.z;
    }
} num[10005];

int main()
{
    
    
    int n,l,i,r=0,sum=0,x=0;
    cin>>n>>l;
    for(i=0; i<n; i++)
        cin>>num[i].z>>num[i].y;
    sort(num,num+n);
    for(i=0; i<n; i++)
    {
    
    
        r=num[i].z;
x:
        while(r<num[i].y)
        {
    
    
            sum++;
            r+=l;
        }
        if(r>num[i+1].z)
        {
    
    
            i++;
            goto x;
        }
    }
    cout<<sum;
    return 0;
}

T
题意:给你n个区间,求一个集合,每个区间至少有两个整数包含在这个集合内,求集合最少包含几个元素。

这个题类似于之前的区间重叠问题,先分析题目,题目说每个区间至少两个整数点,这是一个关键点,考虑每两个区间有没有区间重叠,并讨论重叠处是否有两个点,若存在这两个点,那么这两个点一定可以取第一个区间的最后两个点,所以我们在第一个区间内只需考虑最后两个点与以后区间的位置关系。(这里第一个区间是讨论两个区间里相对的)
首先先按右端点对区间排序,我们先定两个指针l和r,r表示第一个区间的右端点,l表示右端点减一的那个点,如果只有一个区间,集合最少包含2个元素,所以初始值为2。接下来讨论多个区间,(1)如果第二个区间的左端点< l,不管右端点如何,第一个区间一定有两个元素同时存在于第二个区间内,即l和r,如图
在这里插入图片描述
进行下一轮判断。(2)如果第二个区间的左端点在l与r之间,或者等于r,两个区间内只能有一个重合的整数,即r,所以要在第二个区间找另一个元素,即元素数加1,目前为3,同时更新指针,l=r,r等于第二个区间的右端点,即使l和r之间可能有多个整数,但当第三个区间的左端点在l和r之间或等于r时,两区间重合点只考虑一个r。(3)如果第二个区间的左端点大于r,两区间不相交,元素个数加2,相当于更新初始值为4,更新初始区间,l=第二个区间的右端点减1,r=第二个区间右端点。依次类推。

#include<iostream>
#include<algorithm>
using namespace std;
struct n
{
    
    
    int x,y;
    bool operator <(const n &b) const
    {
    
    
        return y<b.y;
    }
} a[10005];
int main()
{
    
    
    int n,i,sum=2,r,l;
    cin>>n;
    for(i=0; i<n; i++)
        cin>>a[i].x>>a[i].y;
    sort(a,a+n);
    l=a[0].y-1;
    r=a[0].y;
    for(i=1; i<n; i++)
    {
    
    
        if(a[i].x>l&&a[i].x<=r)
        {
    
    
            sum++;
            l=r;
            r=a[i].y;
        }
        else if(a[i].x>r)
        {
    
    
            sum+=2;
            l=a[i].y-1;
            r=a[i].y;
        }
    }
    cout<<sum<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_51443397/article/details/114684685