算法入门之贪心(《算法笔记》)

简单贪心

贪心法是求解一类最优化问题的方法,考虑在当前状态下 局部最优(或较优) 来使全局的结果达到最优(或较优)。
👉适用于一定满足最优子结构性质的问题,即一个问题的最优解可以由它的子问题的最优解有效地构造出来。(复习:分治中分解的子问题也是互不相交的)

👇看两个例子

[PAT B1020]月饼(25)

原题目:1020 月饼 (25 分).
题目大意: 给出月饼的种类数、市场最大需求量及每种月饼的库存量和总售价【注意是“总”售价】,要求计算出最大收益(精确到小数点后2位)。
分析: (1)策略:总是选择单价最高的月饼出售以获取最大收益 → ①先计算每种月饼的单价②再按单价从高到低排序。
(2)从单价高的月饼开始枚举 → ①若当前种类的月饼量不能满足需求量,则当前月饼全部卖出,并相应计算剩余需求量和当前累计总收益;②若能满足需求量,则按需卖出该种类月饼,需求量减为0,并计算当前累计总收益。
注意点: ①是否整数;②单位。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
struct mooncake{
    double stock;  //库存量(万吨)
    double sell;  //总售价(亿元)
    double unit;  //单价(亿元/万吨)
};
bool cmp(mooncake a, mooncake b){  //按单价从高到低排列
    return a.unit>b.unit;
}
int main()
{
    int n, need;  //种类,需求量(万吨)
    double profit = 0;  //收益(亿元)
    cin >> n >> need;
    vector<mooncake> v(n);
    for ( int i=0; i<n; i++ ){
        cin >> v[i].stock;
    }
    for ( int i=0; i<n; i++ ){
        cin >> v[i].sell;
        v[i].unit = v[i].sell / v[i].stock;
    }
    sort(v.begin(), v.end(), cmp);
    for ( int i=0; i<n; i++ ){
        if ( v[i].stock <= need ){
            profit += v[i].sell;
        }
        else{
            profit += v[i].unit*need;
            break;
        }
        need -= v[i].stock;
    }
    printf("%.2f", profit);
    
    return 0;
}

👉STL之动态数组vector的使用

#include <vector>
using namespace std;

&1 关于vector数组大小

//1 直接定义长度【且默认所有元素值为0】
vector<int> v[10];
//1'
int n = 10;
vector<int> v(n);

//2 调用resize
vector<int> v;
v.resize(10);

//3 定义时初始化
vector<int> v(100, 9); //所有100个元素值都为9

//4 通过puch_back在数组末尾添加一个元素
vector<int> v;
for ( int i=0; i<10; i++ ){
	v.push_back(i);
}

//5 调用size()获取vector数组长度
cout << v.size();  //容器大小

【注】vector作二维数组

  1. 用push_back扩展vector数组成二维数组时,需要在定义时就声明vector数组的一维大小,不能用resize声明。
  2. 若用resize声明数组大小,则定义时元素类型应如下👇
vector<vector<int>> v;  //***
v.resize(n);
v[1].push_back(3);

【注】用常数声明vector大小用中括号

const int maxv = 510;
vector<int> pre[maxv];

&2 关于访问vector数组

v.begin()是指向容器的第一个元素的指针;v.end()则指向容器的最后一个元素的后一个位置

//1
sort(v.begin(), v.end());

//2
for ( auto it=v.begin(); it!=v.end(); it++){
	cout << *it << " ";
}//注意it这里需要像访问指针一样来访问它

&3 vector做形参时的三种传参方式

fun1(vector<int> v);  //传值
fun2(vector<int> &v);  //传引用
fun3(vector<int> *v):  //传指针

//调用形式分别为
fun1(v);
fun2(v);
fun3(&v):

&4 vector增删元素

//1 在末尾增删单个元素,时间复杂度为O(1)
v.push_back(x);
v.pop_back();

//2 清空clear()
v.clear();

&5 vector拷贝/复制
【注】留两个问题:
①深复制与浅复制的区别?(好像说vector本身完全杜绝浅拷贝)
②复制前是否清空数据的区别?

//1 利用拷贝赋值
vector<int> v1{1,2,3,4};
vector<int> v2;
v2 = v1;

//2 利用拷贝构造
vector<int> v1{1,2,3,4};
vector<int> v2(v1);

//3 利用swap()交换(会先清空)
vector<int> v1{1,2,3,4};
vector<int> v2;//设为空
v2.swap(v2);//清空v1数组

//4 利用assign()将另一个数组的值拷贝过来
vector<int> v1{1,2,3,4};
vector<int> v2{4,5,6};
v2.assign(v1.begin(),v1.end());//清空原数据,赋予新数据为{4,5,6}
v2.assign(5, 0);//5个0,清空原数据后为{0,0,0,0,0}

[PAT B1023]组个最小数(20)

原题目:1023 组个最小数 (20 分).
题目大意: 分别给出数字0-9的个数,要求用这些数字组成最小数(注意0不能做首位)。
分析: 先选出不为0的最小数输出,再依次输出0-9。

#include <iostream>
using namespace std;
int main()
{
    int a[10];
    for ( int i=0; i<10; i++ ) cin >> a[i];  //录入各数字的个数
    for ( int i=1; i<10; i++ ){
        if ( a[i]!=0 ){
            cout << i;
            a[i]--;
            break;
        }
    }
    for ( int i=0; i<10; i++ ){
        if ( a[i]!=0 ){
            do{
                cout << i;
            }while(--a[i]);
        }
    }
    
    return 0;
}

区间贪心

区间不相交问题

问题描述: 给出N个开区间(x,y),从中选择尽可能多的开区间,使得这些开区间两两没有交集。
分析: 为了有更大的空间去容纳其他开区间,总是先选择左端点最大的开区间(可借助画图的方式来分析不同情况);当左端点相同时,总是先选择右端点小的区间——即用排序,先左端点从大到小排序,左端点相同时再按右端点从小到大排序。【总是先选择右端点最小的区间也可】

👇搬运个代码

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
struct Interval{
	int x, y;  //开区间左右端点 
};
bool cmp(Interval a, Interval b){
	if ( a.x!=b.x ) return a.x>b.x;  //先按左端点从大到小排序
	else return a.y<b.y;   //左端点相同的按右端点从小到大排序 
}
int main()
{
	int n;
	cin >> n;
	vector<Interval> v(n);
	for ( int i=0; i<n; i++ ){
		cin >> v[i].x >> v[i].y;
	}
	sort(v.begin(), v.end(), cmp);  //给区间排序 
	int ans = 1;  //记录不相交区间个数,至少有一个
	int lastX = v[0].x;   //记录上一个被选中区间的左端点
	for ( int i=1; i<n; i++ ){
		if ( v[i].y<=lastX ){  //如果该区间右端点在lastX左边 
			lastX = v[i].x;   //以v[i]作为新选中的区间
			ans++; 
		}
	} 
	cout << ans;
	
	return 0;
}

【注意】在选中新区间时,应用“<=”作为判断条件,因为选的是“开”区间,所以可取到等号。

👉类似区间选点问题

问题描述: 给出N个闭区间[x,y],求最少需要确定多少个点才能使每个闭区间中至少存在一个点。
分析: 策略与区间不相交问题一致;取尽可能多地覆盖其他区间的点。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值