简单贪心
贪心法是求解一类最优化问题的方法,考虑在当前状态下 局部最优(或较优) 来使全局的结果达到最优(或较优)。
👉适用于一定满足最优子结构性质的问题,即一个问题的最优解可以由它的子问题的最优解有效地构造出来。(复习:分治中分解的子问题也是互不相交的)
👇看两个例子
[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作二维数组
- 用push_back扩展vector数组成二维数组时,需要在定义时就声明vector数组的一维大小,不能用resize声明。
- 若用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],求最少需要确定多少个点才能使每个闭区间中至少存在一个点。
分析: 策略与区间不相交问题一致;取尽可能多地覆盖其他区间的点。