王道机试第7章 贪心策略

贪心策略:总是做出当前最好的策略
1.问题分解为多个子问题
2.子问题求局部最优解
3.局部最优解组合成原问题的解
【不能保证全局最优解】

简单贪心

例题7.2 尽可能多的咖啡豆:贪心策略
注意:商品可任意切分,无需买完,若必须买完,则成背包问题
在这里插入图片描述
关于传数组:
1.可以向书中一样设置全局数组,这样就可以不用传
2.关于传数组
3.关于传递二维数组:
数组传递的用法,不熟,加强记忆

关于c++中找数组最大最小值,因为需要知道序号,所以只能用笨方法:

/*
知识点:bool 类型 命名空间 输入输出 
题目要求:使用函数找出一个整型数组中的最大值或最小值 
*/ 
 
#include<stdlib.h>
#include<iostream>
using namespace std;
 
int getMaxOrMin(int *arr,int count,bool isMax){
	int temp=arr[0];
	for(int i=1;i<count;i++){
		if(isMax){
			if(temp<arr[i]){
			temp=arr[i];
			}
		}else{
			if(temp>arr[i]){
			temp=arr[i];
			}			
		}	
	}
	return temp;	
}

int main(void){
	int arr1[4]={3,5,1,7};
	bool isMax=false;
	cin>>isMax;//从键盘接收 
	cout<<getMaxOrMin(arr1,4,isMax)<<endl;//输出 
	system("pause");
	return 0;
} 

问题:C++如何表示无限大?

1 正无穷大即比任何其他的数都大,所以在c语言中特定数据类型的正无穷大则可以用该数据类型的最大值来表示
2 借助limits.h库,里面定义了各种数据类型的最大值
3 部分数据类型及其对应的最大值如下
int->INT_MAX
unsigned int ->UINT_MAX
long->LONG_MAX
unsigned long->ULONG_MAX

关于两个整数相除如何得到小数:

            //错误:num += A[minnum][0] * (double)(M / A[minnum][1]);
            num += (double)(A[minnum][0] * M) / A[minnum][1];

int输出%f浮点值的时候,比如2,2内部表示如果看作是float,是个很小的数所以输出的是0.000000

自己写的(修改了很久):

#include <iostream>
#include <cstdio>
#include <string>


using namespace std;

//找当前数组中最小值的序号
int MinNum(double B[], int size){
    double tmp = B[0];
    int minnum = 0;
    for(int i = 1; i < size; i++){
        if(B[i] < tmp){
            tmp = B[i];
            minnum = i;
        }
    }
    return minnum;
}

double TX(int size, int M,int A[][2]){                    //注意传递数组
    //打印A看看
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 2; j++){
            printf("%d ",A[i][j]);
        }
        printf("\n");
    }
    double num = 0;//总数量
    double B[size];
    memset(B,0,sizeof (B));
    for(int i = 0; i < size; i++){
        B[i] = (double)A[i][1] / A[i][0];                      //存的每个房间的单价
    }
    while (M){
        //要找单价最小的后并置为无限大
        int minnum =  MinNum(B, size);
        if(M >= A[minnum][1]){//若能把房间买空
            M -= A[minnum][1];
            num += A[minnum][0];
        }else{                 //若钱不够
            //错误:num += A[minnum][0] * (double)(M / A[minnum][1]);
            num += (double)(A[minnum][0] * M) / A[minnum][1];
            M = 0;
        }
        //该房间单价置为无限大
        B[minnum] = 1000;
    }
    for(int i = 0; i < 3; i++){
        for(int j = 0; j < 2; j++){
            printf("%d ",A[i][j]);
        }
        printf("\n");
    }
    return num;
}

int main(){
    int m,n;
    while (scanf("%d %d", &m,&n) != EOF){
        if (m == -1 && n == -1){
            break;
        }

        //用一个二维数组存储
        int Food[1000][2];

        for(int i = 0; i < n; i++){
            scanf("%d %d", &Food[i][0], &Food[i][1]);
        }

        //贪心算法计算M磅猫粮最多可以获得多少咖啡豆
        double sum = TX(n, m, Food);
        printf("%.3f\n",sum);
    }
    return 0;
}

/*
5 3
7 2
4 3
5 2
20 3
25 18
24 15
15 10
-1 -1

 */

老师写的:(发现我自己写的有多自找麻烦)
1.不用二维数组,用结构体数组,问题少90%

2.直接用排序,改变数组顺序也没问题,因为也不要求你的返回数组现状

3.注意输入重量和价格的时候也是用的double变量,中间有需要用到除法的地方一律之前设置为double,问题又少一半

4.double输入的格式:%lf ,保留三位小数输出的格式:%.3f

5.万一不得已用了二维数组,传数组的时候注意要传二维数组第二个长度,用于编译器划分,且注意二维数组传参后函数内部看不到原二维数组,因为传过去的就是地址值。

6.sort 加头文件 #include <algorithm>本地IDE能通过,但是OJ不一定能通过,产生CE。

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

//不用二维数组来存储质量和价格,用结构体方便很多
struct JavaBean {
    double weight;
    double cost;
};

const int MAXN  = 1000;

JavaBean arr[MAXN];

//注意sort和Compare函数的写法,又有遗忘
bool Compare(JavaBean a, JavaBean b){
    return a.cost / a.weight < b.cost / b.weight;
    //老师的写法更为简洁
//    if(a.cost / a.weight < b.cost / b.weight){
//        return true;
//    } else
//        return false;
}

int main(){
    int n, m;
    while(scanf("%d%d", &m, &n) != EOF){
        if(n == -1 && m == -1 ){
            break;
        }
        for(int i = 0; i < n; i ++){
            scanf("%lf%lf", &arr[i].weight, &arr[i].cost);
        }
        sort(arr, arr + n, Compare);
        double answer = 0;
        for (int i = 0; i < n; i++){
            if(m >= arr[i].cost) {
                m -= arr[i].cost;
                answer += arr[i].weight;
            } else {
                answer += arr[i].weight * (m / arr[i].cost);
                m = 0;
                break;
            }
        }
        printf("%.3f\n", answer);
    }
    return 0;
}

例题2雪姐姐杀怪兽:

在这里插入图片描述
我的:

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


/*
 * 思路:可以根据遍历枪:(不需要关注枪和怪兽数量大小比较)
 * 选出该枪对应的最小防御值的怪兽的编号,并存储差值bonus;
 * 一次遍历夏下来后查看有无重复怪兽编号的
 * 如果有再对比这两个枪的bonus,bonus小的重新选怪兽,直至没有重复的怪兽编号
 *(过程中如果有枪的攻击值没有对应比他小的怪兽防御值,则将该枪置为false)
 * 之后对
 */
const int Maxn = 100000;

struct Gun{
    int num;
    int att;
    bool use = false;           //初值为false,表示不用
    int bonus;                  //获利值
};
//结构体数组怎么定义?
Gun gun[Maxn];

struct Monster{
    int num;
    int def;
};

Monster monster[Maxn];


int main() {

    return 0;
}

老师的分析比我的简单很多:
1.空间应该比100000大一些,因为可以取到100000;
2.老师用的是把枪按从大到小排序,把怪兽从小到大排序,方便很多;

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

const int Maxn = 100001;

long long gun[Maxn];
long long monster[Maxn];

bool Compare(long long x, long long y){
    return x > y;
}

int main(){
    int caseNumber;
    scanf("%d", &caseNumber);
    while (caseNumber--){
        int n, m;
        scanf("%d%d", &n,&m);
        //注意 long long 输入格式
        for(int i = 0; i < n; i++){
            scanf("%lld", &gun[i]);
        }
        for(int i = 0; i < m; i++){
            scanf("%lld", &monster[i]);
        }
        sort(gun, gun + n, Compare);
        sort(monster,monster + m);      //sort是默认从小到大排序的
        long long ans = 0;
        for (int i = 0; i < n; i++){
            if (i >= m || gun[i] < monster[i]){
                break;
            }
            ans += (gun[i] - monster[i]);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

/*
1
2 2
2 3
2 2
 */

例题3 箱子打包
题目
在这里插入图片描述

思路:
1.每次装一个箱子
2.每次选最大和最小的物品装在同一个箱子,如果溢出,则只装最大物品

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int MAXN = 1e5 + 10;

int length[MAXN];

int main(){
    int n,l;
    scanf("%d%d",&n,&l);
    for(int i = 0; i < n; i++){
        scanf("%d", &length[i]);
    }
    sort(length, length + n);
    int left = 0;
    int right = n - 1;
    int ans = 0;
    while(left <= right){           //还有物品没有装完
        if(length[left] + length[right] <= l) {
            left++;
            right--;
        } else{
            right--;
        }
        ans++;
    }
    printf("%d\n", ans);
    return 0;
}

/*
*/

最大的最小间距问题: 二分策略
最大值问题 ->判定型问题
给定一个间距d,判断其是否可行,对间距d采取二分策略

下题分析:
二分法原因:相当于在一个数组中查找一个满足条件的值(时间复杂度上较小)

/*
题意:
    有n个牛栏,选m个放进牛,相当于一条线段上有 n 个点,选取 m 个点,
使得相邻点之间的最小距离值最大
思路:贪心+二分
    二分枚举相邻两牛的间距,判断大于等于此间距下能否放进所有的牛。

Sample Input

5 3
1
2
8
4
9
Sample Output

3
*/
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

//求间距使用减法不会使用加法,所以不会超出Int
const int MAXN = 1e5 + 10;

int arr[MAXN];
//判定函数,n个房间,m头牛,牛之间最小间距为distance,这样的设置是否合理
bool Judge(int n, int m, int distance){
    int current = arr[0];
    int num = 1;
    for(int i = 1; i < n; i++){
        if(arr[i] - current >= distance){
            num++;
            current = arr[i];
        }
        if(num >= m){
            return true;
        }
    }
    return false;
}

int main(){
    int n,m;            //n间房间,m头牛
    while(scanf("%d%d", &n, &m) != EOF){
        for(int i = 0; i < n; i++){
            scanf("%d", &arr[i]);
        }
        sort(arr, arr + n);
        //对distance进行二分
        int left = 1;
        int right = arr[n - 1] - arr[0];
        while (left <= right){
            int middle = left + (right - left) / 2;
            if(Judge(n,m,middle)) {
                left = middle + 1;
            } else {
                right = middle - 1;
            }
        }
        printf("%d\n", right);
    }
    return 0;
}

/*
2 2
1 9
*/

例题 POJ304 题目

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

const int MAXN = 1e5 + 10;

int water[MAXN];                                    //每件衣服的含水量

//判断函数:若衣服数量为n,烘干机每分钟烘干为k,在time时间内可否全部烘干
bool Judge(int n, int k, int time){
    int sum = 0;        //时间
    for(int i = 0; i < n; i++){
        //if(water[i] <= time)                      //此种情况不需要烘干机
        if(water[i] > time){                        //需要使用烘干机
          sum += ceil((water[i] - time) * 1.0 / (k - 1));       //注意是k-1,如果不进入烘干机,也会自然晾干1
        }
        //除法是取下整,但是我们需要取上整数
        //ceil函数只能处理浮点数,所以在分子部分乘上1.0
        //ceil函数需要头文件cmath
        if(sum > time){                             //如果花在烘干机上的时间大于所给的时间
            return false;
        }
    }
    return true;
}

int main(){
    //也是二分+判定:
    int n;
    while (scanf("%d", &n) != EOF){
        for(int i = 0; i < n; i++){
            scanf("%d", &water[i]);
        }
        int k;
        scanf("%d", &k);                            //烘干机每分钟烘干
        sort(water, water + n);                 //排一下序
        if(k == 1){
            printf("%d\n", water[n - 1]);
        } else {
            int left = 1;
            int right = water[n - 1];
            while ( left <= right){
                int middle = left + (right - left) / 2;
                if (Judge(n, k, middle)){               //如果能烘干,则减小烘干时间区间
                    right = middle - 1;
                } else {
                    left = middle + 1;
                }
            }
            printf("%d\n", left);
        }
    }
    return 0;
}

/*
Sample Input
sample input #1
3
2 3 9
5
sample input #2
3
2 3 6
5
Sample Output
sample output #1
3
sample output #2
2
*/

区间贪心

区间贪心和普通贪心的区别,值不止一个:如此题有开始时间、结束时间、节目时间长短,应该怎么贪心?
考察的不是一个数据,而是一个区间
考虑每一个子问题,当前该选择哪一个节目:
1.选择开始时间最早的–不可行
2.选择持续时间最短的–不可行
3.选择结束时间最早的–正确,能获得局部最优解

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

const int MAXN = 100 + 10;

//需要用到的数组最好定为全局变量,不用单独传数组,方便

struct program {
    int startTime;
    int endTime;
};

//定义一个结构体数组
program arr[MAXN];

bool Compare(program a, program b){
    return a.endTime < b.endTime;
}

int main(){
    int n;
    while(scanf("%d", &n) != EOF){
        if(n == 0){                         //特定跳出
            break;
        }
        for (int i = 0; i < n; i++ ){      //输入节目的开始时间和结束时间
            scanf("%d%d",&arr[i].startTime, &arr[i].endTime);
        }
        sort(arr, arr + n, Compare);
        int current = 0;
        int answer = 0;
        for(int i = 0; i < n; i++){
            if(current <= arr[i].startTime) {
                current = arr[i].endTime;
                answer++;
            }
        }
        printf("%d\n",answer);
    }
    return 0;
}

例题2题目

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

const int MAXN = 1000 + 10;

//定义可以安装雷达的区间结构体
struct Interval {
    double left;
    double right;
};

Interval arr[MAXN];

//排序的规则:左端点小的在前面
bool Compare(Interval a, Interval b){
    return a.left < b.left;
}

int main(){
    int n,d;
    int caseNumber = 0;
    while(scanf("%d%d", &n, &d) != EOF) {
        if(n == 0 && d == 0) {
            break;
        }
        bool flag = true;                               //是否有解的标志位
        for(int i = 0; i < n; i++){
            int x, y;
            scanf("%d%d", &x, &y);
            //if(pow(d,2) - pow (y, 2) < 0){          //无解,写复杂了,直接y > d
            if(y > d){
                flag = false;
//                printf("-1\n");
//                break;
            } else {
                arr[i].left = x - sqrt((double)(pow(d,2) - pow(y, 2)));
                arr[i].right = x + sqrt((double)(pow(d,2) - pow(y, 2)));
                //老师写得更简便(d * d - y * y)
                //注意sqrt参数是double型,1.可以强制转化为double型 2.可以乘以一个1.0
            }
        }
        if(!flag) {
            printf("Case %d: %d\n", ++caseNumber, -1);
        } else {
            sort(arr, arr + n, Compare);
            double current = arr[0].right;
            int answer = 1;                     //雷达数目
            for (int i = 1; i < n; i++) {
                if (arr[i].left <= current) {    //第二个区域的左边有重叠
                    current = min(current, arr[i].right);
                } else {
                    current = arr[i].right;
                    answer++;
                }
            }
            printf("Case %d: %d\n", ++caseNumber, answer);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

gylagyl97

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值