02.01 洛谷贪心算法例题

1.P1090合并果子

题意:在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。

每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过n-1次合并之后,就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。

因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为1,并且已知果子的种类数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。

分析:从给出的个数里每次取两个最小的数相加,并将和放入数列;以下代码借助优先队列

AC代码

#include<bits/stdc++.h>
using namespace std;
priority_queue<int,vector<int>,greater<int> > q;//定义一个优先为小的优先队列
int main()
{
    int n,x,a,b,ans=0;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>x;
        q.push(x);
    }
    for(int i=1;i<n;i++)
    {
        a=q.top();
        q.pop();
        b=q.top();
        q.pop();
        ans+=a+b;
        q.push(a+b);
    }
    cout<<ans<<endl;
    return 0;
}
##优先队列:

¢ 优先队列容器与队列一样,只能从队尾插入元素,从队首删除元素。但是它有一个特性,就是队列中最大的元素总是位于队首,所以出队时,并非按照先进先出的原则进行,而是将当前队列中最大的元素出队。
¢ 元素的比较规则默认按元素值由大到小排序,可以重载“<”操作符来重新定义比较规则。

¢头文件: #include <queue>

   定义priority_queue<data_type> priority_queue_name; 如:priority_queue<int> q;//默认是大顶堆

  操作q.push(elem) 将元素elem置入优先队列          q.top() 返回优先队列的下一个元素

              q.pop() 移除一个元素                q.size() 返回队列中元素的个数

              q.empty() 返回优先队列是否为空

2.P1181 数列分段Section

题意 对于给定的一个长度为N的正整数数列A[i],现要将其分成连续的若干段,并且每段和不超过M(可以等于M),问最少能将其分成多少段使得满足要求。输入n(a[i]元素个数),m(最大值),n个数;输出:最少分段数
AC代码
#include<bits/stdc++.h>
using namespace std;
int a[100010];
int main()
{
    int n,m,sum=0,ans=0;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
       cin>>a[i];
    for(int i=1;i<=n;i++)
    {
        sum+=a[i];
        if(sum+a[i+1]>m){ans++;sum=0;}
    }
    if(sum!=0)ans++;//注意若最后sum不为0,则ans要加一
    cout<<ans<<endl;
    return 0;
}

3.P1208混合牛奶

题意Marry乳业从一些奶农手中采购牛奶,并且每一位奶农为乳制品加工企业提供的价格是不同的。此外,就像每头奶牛每天只能挤出固定数量的奶,每位奶农每天能提供的牛奶数量是一定的。每天Marry乳业可以从奶农手中采购到小于或者等于奶农最大产量的整数数量的牛奶。 给出Marry乳业每天对牛奶的需求量,还有每位奶农提供的牛奶单价和产量。计算采购足够数量的牛奶所需的最小花费。

注:每天所有奶农的总产量大于Marry乳业的需求量。输入:n需要的牛奶总量,m提供牛奶的农民总数,m个农民分别的牛奶单价及牛奶量

分析:将单价升序排列,由最小单价开始买,注意数组的大小,太大太小都RE
AC代码
#include<bits/stdc++.h>
using namespace std;
struct milk{
    int price;//牛奶单价
    int num;//牛奶数量
    int value;//花销
}a[5000010];
bool cmp(milk a,milk b)//单价由小到大排序
{
    return a.price<b.price;
}
int main()
{
    int n,m,money=0,sum=0;//n需要牛奶的总数 m农民数2
    cin>>n>>m;
    if(n==0||m==0){cout<<0<<endl;return 0;}
    for(int i=1;i<=m;i++)
    {
        cin>>a[i].price>>a[i].num;
        a[i].value=a[i].price*a[i].num;
    }
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++)
    {
        money+=a[i].value;
        sum+=a[i].num;
        if(sum==n){cout<<money<<endl;return 0;}
        if(sum>n){sum-=a[i].num;money-=a[i].value;n-=sum;money+=n*a[i].price;cout<<money<<endl;return 0;}
    }
}

4.P1223排队接水

题意有n个人在一个水龙头前排队接水,假如每个人接水的时间为Ti,请编程找出这n个人排队的一种顺序,使得n个人的平均等待时间最小
分析:将时间升序排列
AC代码
#include<bits/stdc++.h>
using namespace std;
struct water
{
    double t;
    int k;
}a[1010];
bool cmp(water c,water b)
{
    return c.t<b.t;
}
int main()
{
    int n;
    double ans,sumt=0;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i].t;
        a[i].k=i;
    }
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++)
    {
        cout<<a[i].k<<' ';
        if(i!=n){sumt+=a[i].t;ans+=sumt;}

    }
    ans=ans/n;
    cout<<endl;
    //printf("%0.2lf\n",ans);
    cout<<fixed<<setprecision(2)<<ans<<endl;
    return 0;
}
##cout的格式化输出
#include<iostream>
#include<iomanip>
using namespace std;
int main()
{
    const int p=1.2345678,a=1234678;
    cout<<hex<<a<<endl;//以十六进制形式输出
    cout<<dec<<a<<endl;//以十进制形式输出
    cout<<oct<<a<<endl;//以八进制形式输出
    cout<<setw(5)<<p<<endl;//控制输出宽度为5,相当于printf("%5d",p);
    cout<<setprecision(5)<<p<<endl;//控制输出有效数字的个数为5
    cout<<fixed<<setprecision(5)<<p<<endl;//fixed表示控制小数点后面的数字,连用表示保留五位小数
    cout<<hex<<uppercase<<a<<endl;//uppercase输出十六进制的大写字符
    cout<<left<<setw(10)<<a<<endl;//控制结果左对齐输出
    cout<<right<<setw(10)<<a<<endl;//控制结果右对齐输出
    cout<<setfill('a')<<setw(10)<<a<<endl;//设置填充字符为a
    return 0;
}


5,P1094纪念品分组

题意元旦快到了,校学生会让乐乐负责新年晚会的纪念品发放工作。为使得参加晚会的同学所获得 的纪念品价值相对均衡,他要把购来的纪念品根据价格进行分组,但每组最多只能包括两件纪念品, 并且每组纪念品的价格之和不能超过一个给定的整数。为了保证在尽量短的时间内发完所有纪念品,乐乐希望分组的数目最少。

你的任务是写一个程序,找出所有分组方案中分组数最少的一种,输出最少的分组数目。

输入:n纪念品之和上限,m纪念品总数,m个纪念品的价格 输出:最少分组数
分析:每组最多两件,排序后,取每次的最大值和最小值组合,若小于等于上限,则分为一组,若大于上限,则说明最大值只能自己一组。
AC代码
#include<bits/stdc++.h>
using namespace std;
int a[3000010];
void qsort(int l,int r)//快排
{
    int i,j,mid;
    i=l;j=r;mid=a[(l+r)/2];
    do
    {
        while(a[i]<mid)i++;
        while(a[j]>mid)j--;
        if(i<=j)
        {
            swap(a[i],a[j]);
            i++;j--;
        }
    }while(i<=j);
    if(l<j)qsort(l,j);
    if(i<r)qsort(i,r);
}
int main()
{
    int n,m,i,j,ans=0;
    cin>>n>>m;
    i=1,j=m;
    for(int k=1;k<=m;k++)
        cin>>a[k];
    qsort(1,m);
    while(i<=j)
    {
        if(a[i]+a[j]<=n){ans++;i++;j--;}//见分析
        else{ans++;j--;}
    }
    cout<<ans<<endl;
    return 0;
}

6.凌乱的YYY


题意:现在各大oj上有n个比赛,每个比赛的开始、结束的时间点是知道的。yyy认为,参加越多的比赛,noip就能考的越好(假的)所以,他想知道他最多能参加几个比赛。由于yyy是蒟蒻,如果要参加一个比赛必须善始善终,而且不能同时参加2个及以上的比赛。  输入:活动个数n  输出:n个活动的起始时间和结束时间
分析:将活动结束时间升序排序,判断每个结束时间是否与前一个起始时间重合
AC代码
#include<iostream>
using namespace std;
int n,begin[1000010],end[1000010];
void init()//输入函数
{
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>begin[i]>>end[i];
}
void qsort(int x,int y)//快排
{
    int i,j,mid,t;
    i=x;j=y;mid=end[(x+y)/2];
    while(i<=j)
    {
        while(end[i]<mid)i++;
        while(end[j]>mid)j--;
        if(i<=j)
        {
            swap(end[i],end[j]);
            swap(begin[i],begin[j]);
            i++;j--;
        }
    }
    if(x<j)qsort(x,j);
    if(i<y)qsort(i,y);
}
void solve()//处理
{
    int ans=0,t=-1;
    for(int i=1;i<=n;i++)
        if(begin[i]>=t){ans++;t=end[i];}
    cout<<ans<<endl;
}
int main()
{
    init();
    qsort(1,n);
    solve();
    return 0;
}
超时代码:

#include<bits/stdc++.h>
using namespace std;
struct time{  //定义时间结构体
    int begin;
    int end;
}a[1000010];
void qsort(int l,int r)//快排
{
    int i,j,mid;
    i=l;j=r;mid=a[(l+r)/2].end;
    do{
        while(a[i].end<mid)i++;
        while(a[j].end>mid)j--;
        if(i<=j)
        {
          swap(a[i].end,a[j].end);
          i++;j--;
        }
    }while(i<=j);
    while(l<j)qsort(l,j);
    while(i<r)qsort(i,r);
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i].begin>>a[i].end;//输入起始时间和结束时间
    qsort(1,n);//对结束时间进行升序排序
    int ans=0,t=-1;//令t=-1可以使第一个区间与其他区间操作相同
    for(int i=1;i<=n;i++)
        if(a[i].begin>=t){ans++;t=a[i].end;}
    cout<<ans<<endl;
    return 0;
}

7.P1031均分纸牌


题意有 N 堆纸牌,编号分别为 1,2,…, N。每堆上有若干张,但纸牌总数必为 N 的倍数。可以在任一堆上取若干张纸牌,然后移动。

移牌规则为:在编号为 1 堆上取的纸牌,只能移到编号为 2 的堆上;在编号为 N 的堆上取的纸牌,只能移到编号为 N-1 的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。

现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。

分析:求总张数--->求平均数--->求各堆纸牌与平均数的差值--->左往右加(注意去除前导后导0,及中间0的判断)
AC代码
#include<bits/stdc++.h>
using namespace std;
int a[110];
int main()
{
    int n,ave=0;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        ave+=a[i];//求总张数
    }
    ave/=n;//求平均数
    for(int i=1;i<=n;i++)
        a[i]-=ave;//求每堆纸牌与平均数差值
    int i=1,j=n,step=0;
    while(a[i]==0&&i<n)i++;
    while(a[j]==0&&j>1)j--;//去除前导后导0
    while(i<j)
    {
        a[i+1]+=a[i];//将第i堆纸牌移到第i+1堆上
        a[i]=0;//第i堆牌移走后变为0
        step++;//计步数
        i++;//对下一堆纸牌进行循环操作
        while(a[i]==0&&i<j)i++;//过滤移牌过程中产生的0
    }
    cout<<step<<endl;
    return 0;
}

8,P1080国王游戏


题意恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏。首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。然后,让这 n 位大臣排成一排,国王站在队伍的最前面。排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。

国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。注意,国王的位置始终在队伍的最前面。

输入格式:第一行包含一个整数 n,表示大臣的人数。第二行包含两个整数 a和 b,之间用一个空格隔开,分别表示国王左手和右手上的整数。接下来 n 行,每行包含两个整数 a 和 b,之间用一个空格隔开,分别表示每个大臣左手和右手上的整数。
输出格式:输出只有一行,包含一个整数,表示重新排列后的队伍中获奖赏最多的大臣所获得的金币数。

分析:(抄袭)

我们对于国王身后的两个点来分析

队列可能是这样的:

* Left Right
king: a 0 a_0 b 0 b_0
p1 a 1 a_1 b 1 b_1
p2 a 2 a_2 b 2 b_2

那么我们计算可得 a n s 1 ans_1  = m a x ( a 0 b 1 , a 0 a 1 b 2 ) max(\frac{a_0}{b_1},\frac{a_0*a_1}{b_2})

队列也有可能是这样的

* Left Right
king: a 0 a_0 b 0 b_0
p2 a 2 a_2 b 2 b_2
p1 a 1 a_1 b 1 b_1

那么我们计算可得 a n s 2 ans_2  = m a x ( a 0 b 2 , a 0 a 2 b 1 ) max(\frac{a_0}{b_2},\frac{a_0*a_2}{b_1})

我们来对比一下两个答案:

a n s 1 ans_1  = m a x ( a 0 b 1 , a 0 a 1 b 2 ) max(\frac{a_0}{b_1},\frac{a_0*a_1}{b_2})

a n s 2 ans_2  = m a x ( a 0 b 2 , a 0 a 2 b 1 ) max(\frac{a_0}{b_2},\frac{a_0*a_2}{b_1})

可以替换得:

a n s 1 ans_1  = m a x ( k 1 , k 2 ) max(k_1,k_2)

a n s 2 ans_2  = m a x ( k 3 , k 4 ) max(k_3,k_4)

显然我们可以得到:

a 0 a 1 b 2 \frac{a_0*a_1}{b_2}  > a 0 b 2 \frac{a_0}{b_2}

a 0 a 2 b 1 \frac{a_0*a_2}{b_1}  > a 0 b 1 \frac{a_0}{b_1}

即:  k 2 k_2  > k 3 k_3

k 4 k_4  > k 1 k_1

如果 a n s 1 ans_1  < a n s 2 ans_2

那么易得:

k 4 > k 2 k_4>k_2

即:  a 0 a 2 b 1 \frac{a_0*a_2}{b_1}  > a 0 a 1 b 2 \frac{a_0*a_1}{b_2}

变形可得:

a 1 b 1 < a 2 b 2 a_1*b_1<a_2*b_2

a 1 b 1 < a 2 b 2 a_1*b_1<a_2*b_2  时,我们也能够得到 a n s 1 ans_1  < a n s 2 ans_2  的结论

所以,为了 a n s ans  取到最小值,我们需要将 a i b i a_i*b_i  较小的放在前面

那么我们以 a i b i a_i*b_i  为关键字排序即可

同时,统计答案时一定不要忘了写高精度!

AC代码
#include<bits/stdc++.h>
using namespace std;
int n,l=1,a[100010],b[100010],c[100010],g[1000010];
//l表示高精度中字符串的长度
//a表示乘积,b表示左手,c表示右手
void gj1(int x)
//高精乘
{
    for(int i=1;i<=l;i++) g[i]*=b[x];
    //每一个都乘
    for(int i=1;i<=l;i++)
    {
        g[i+1]+=(g[i]/10);
        //进位
        g[i]%=10;
        //进位后处理
    }
    l++;
    //长度加一
    while(g[l]>9)
    {
        g[l+1]+=(g[l]/10);
        //单个位上>9 进位
        g[l]%=10;
        //进位后处理
        l++;
        //长度加一
    }
    if(g[l]==0) l--;
    //数组末位(数的首位)为0,出数组
}
void gj2()
//高精除
{
    for(int i=l;i>=1;i--)
    {
        g[i-1]+=((g[i]%c[n])*10);
        //将前一位%第n位大臣右手给下一位(对于数来说)
        g[i]/=c[n];
        //处理这一位
    }
    while(g[l]==0) l--;
    //处理首位
    if(l==0) printf("1\n");
    //防止减完(测试点2)
}
void qsort(int l,int r)
//快排
{
    int i=l,j=r,mid=a[(l+r)/2];
    while(i<=j)
    {
        while(a[i]<mid) i++;
        while(a[j]>mid) j--;
        if(i<=j)
        {
            swap(a[i],a[j]);
            swap(b[i],b[j]);
            swap(c[i],c[j]);
            //每个人对应的三个数组都要跟着换
            i++; j--;
        }
    }
    if(l<j) qsort(l,j);
    if(i<r) qsort(i,r);
}
int main()
{
    cin>>n;
    cin>>b[0]>>c[0];
    //读入国王,国王不参与排序,所以‘0’
    for(int i=1;i<=n;i++)
    //读入大臣,并计算左手*右手
    {
        cin>>b[i]>>c[i];
        a[i]=b[i]*c[i];
    }
    qsort(1,n);
    //排序
    g[1]=b[0];
    //从国王左手开始乘,赋初值
    for(int i=1;i<n;i++) gj1(i);
    gj2();
    //最后一个得到的最多,所以由n-1个左手乘积/第n个右手乘积
    for(int i=l;i>=1;i--) cout<<g[i];
    //倒序输出
    return 0;
}








猜你喜欢

转载自blog.csdn.net/sdau20171989/article/details/79230721