这篇是按照link来写的,记录一下学习过程~
找给黄焖鸡6元3角钱,我们只有2元5角、1元和1角的硬币,贪心算法:先找一个面值不超过6元3角的最大硬币,即2元5角,然后继续这么做,选出了另一个两元五角,选出了一个1元,选出了三个1角,就是每次要么一直找最小的,要么一直找最大的。
1.当一个问题的最优解包含其子问题的最优解时,它可能时动态规划问题或是贪心算法问题。(最优子结构性质)(我的书上写的是最优性原理)
2.若整体最优解可以用过一系列局部最优的选择来达到,则为贪心算法问题。(贪心选择性质)
3.增长型,不回溯。
4.个人认为最难的地方在于如何不暴力地找到下一个最值。
常见的贪心算法:单源最短路径、最小生成树、哈夫曼编码
判断是否用贪心算法:找非最优解,如果用贪心算法找到一个不是最优解就使用动态规划算法。
如果写贪心算法写的特别复杂,就要考虑适不适合了哈哈哈,因为它比较简单。
查了一下,某乎上说绝大多数贪心算法的问题可以用动态规划做,不清楚,明天问一下老师。
题目:在一个果园里,按果子的不同种类分成了不同的堆。合并规则:每一次合并,可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。合并果子时总共消耗的体力等于每次合并所耗体力之和,要尽可能地节省体力,假定每个果子重量都为 1,输出最小体力耗费值。
例如有 3 种果子,数目依次为 1,2,9。可以先将 1、2 堆合并,新堆数目为 3,耗费体力为 3。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 12,耗费体力为 12。总共耗费体力=3+12=15。
输入格式:第一行整数 n,种类数。第二行包含 n 个整数,用空格分隔,第i个整数 a i a_i ai是第 i 种果子的数目。
输出格式:输出包括一行,最小的体力耗费值。输入数据保证这个值小于 2 31 2^{31} 231。
数据范围:1≤n≤10000, 1≤ a i a_i ai≤20000
example:
input:
3
1 2 9
output:
15
这题是构造哈夫曼树,每次找一列数中最小的两个数相加后在找到相应的位置相加,最后的值为最小体力耗费值,哈夫曼树是经典的贪心算法。先将数目sort排序,选取最小a[i]和a[i+1]的两个相加后然后将和插入从a[i+2]到a[n-1]的数组,然后重复选取a[i+2]开始的数组,直到待选取数组只有一个值。后面的过程也可以sort排序.
#include <iostream>
#include <algorithm>
using namespace std;
int Merge_Fruit(int a[],int n) {
sort (a, a + n);
int num=0;
for (int i = 0; i <n-1; ++i) {
a[i + 1] += a[i];
num+=a[i+1];
sort(a+i+1, a + n);
}
return num;
}
int main() {
int n;
cin >> n;
int fruit[100] = {
0 };
for (int i = 0; i < n; ++i) {
cin >> fruit[i];
}
cout<<Merge_Fruit(fruit, n);
}
后面的部分使用直接插入插入会降低它的时间消耗,可以将后面的部分从O(nlogn)减小到O(n)。
#include <iostream>
#include <algorithm>
using namespace std;
int Merge_Fruit(int a[],int n) {
sort (a, a + n);
int num=0;
for (int i = 0; i <n-1; ++i) {
a[i + 1] += a[i];
num+=a[i+1];
int j=i+2;
while(a[j]<=a[i+1]&&j<n){
j++;
}
//a[i+1]插到a[j-1]和a[j]之间
int temp=a[i+1],k;
for(k=i+1;k<j-1;k++){
a[k]=a[k+1];
}
a[k]=temp;
}
return num;
}
int main() {
int n;
cin >> n;
int fruit[100] = {
0 };
for (int i = 0; i < n; ++i) {
cin >> fruit[i];
}
cout<<Merge_Fruit(fruit, n);
}
这里视频里提到了优先队列# i n c l u d e < q u e u e > include
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <queue>
using namespace std;
int Merge_Fruit(priority_queue<int, vector<int>, greater<int> > a, int n) {
int num = 0, first, second;
while(a.size()>1) {
first = a.top();
a.pop();
second = a.top();
a.pop();
num += first + second;
a.push(first + second);
}
return num;
}
int main() {
int n, temp;
cin >> n;
priority_queue<int, vector<int>, greater<int> >fruit;
for (int i = 0; i < n; ++i) {
cin >> temp;
fruit.push(temp);
}
cout << Merge_Fruit(fruit, n);
}
题目:对于给定的一个长度为N的正整数数列 A i A_i Ai,现要将其分成练习的若干段,并且每段和不超过M(可以等于M),问最少能将其分成多少段使得满足要求。
输入格式:第1行包含两个正整数N,M,表示了数列 A i A_i Ai的长度与每段和的最大值,第2行包含N个空格隔开的非负整数 A i A_i Ai,如题目所述。
输出格式:一个正整数,输出最少划分的段数。
example:
input:
5 6
4 2 4 5 1
output:
3
由于每段需要连续,所以每次分段的和是不超过M的最大和,从输入的左边开始,一旦连续子段和超过M就进行分段,分段数加一。
#include <iostream>
using namespace std;
int main() {
int n, m,num=0,k=0,x;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> x;
num += x;
if (num > m) {
k++;
num = x;
}
}
if(num!=0) k++;
cout << k;
}
使用while循环:
#include <iostream>
using namespace std;
int main() {
int n, m,num=0,k=0,x;
cin >> n >> m;
while(cin>>x){
num += x;
if (num > m) {
k++;
num = x;
}
}//因为是正整数,所以只要输入num就不会为0
//当num==0时,没有输入,k=0;
if(num!=0) k++;
cout << k;
}
这里主要需要n没起到什么作用,因为没有索引,注意终止输入时可以Ctrl+Z/D/Y…我目前试了这三个都行,C/F不行,是特殊快捷键。
降低乳制品原材料牛奶的价格,找到最优的牛奶采购方案。从奶农手中采购牛奶,每位奶农提供的牛奶价格不同,由于奶牛产奶量每天不是无限制的,所以每位奶农每天能提供的牛奶的数量是一定的,所以只能采购到小于等于奶农最大提供量的整数数量的牛奶。(假设每天所有奶农的总产量大于采购需求量)
输入:第一行两个整数n,m,表示需要的牛奶总量和奶农个数。接下来m行,没两行整数 p i p_i pi, a i a_i ai,表示第i个农民牛奶的单价,和农民i一天最多能卖的牛奶量。
输出:单独一行包含一个整数,表示拿到满足(>=)所需的牛奶所要的最小费用。
example:
input:
100 5
5 20
9 10
3 10
8 80
6 30
output:
630
该问题属于背包问题,但不是0/1背包问题,因为可以只取一部分,不全部取走。每次都取单价最低的,就可使总费用最低了。
这里总结一下背包问题的解法:离散的都使用动态规划算法,因为很难设计一个贪心算法找到最优解,连续的可以使用贪心算法。什么叫做离散的呢,比如把十种物品放入有最大承受重量的背包,他们有着不同的重量和价值,0/1背包就是说每种物品只有一件,完全背包是指每种物品有无数件,这两种问题中,单个物品是不可拆分的,就是离散的,像是整块黄金。而连续的是都是黄金粉,你想拿多少拿多少,不够了再从"好"的里面抓一点就可以了,这样的问题用贪心法就可以解决。本题中牛奶可以一份一份的买,如果不够了,我们再补上缺少的份数即可。属于部分背包问题。
综上:0/1背包、完全背包使用动态规划,部分背包使用贪心算法。
这里用了vector存放结构体变量,参考vector容器中存放结构体变量当然直接用数组更简单,我是想多练习一下vector
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <algorithm>
#include <queue>
using namespace std;
struct Dairy_farmer {
int p;
int a;
};
bool cmp(Dairy_farmer a, Dairy_farmer b) {
return a.p < b.p;
}
int main() {
vector<Dairy_farmer> farmers;
int n, m;
Dairy_farmer x;
cin >> n >> m;
for (int i = 0; i < m; i++) {
cin >> x.p >> x.a;
farmers.push_back(x);
}
sort(&farmers[0], &farmers[0] + m, cmp);
int num = 0, sum = 0;
for (int i = 0; i < m; i++) {
num += farmers[i].a;
sum += farmers[i].p * farmers[i].a;
if (num > n) {
sum -= farmers[i].p * (num - n);
break;
}
}
cout << sum;
}
当然,在C++中,这个结构体可以写一个class,也可以写一个pair可以参考C++ pair的用法
#include <iostream>
#include <algorithm>
using namespace std;
bool cmp(pair<int, int> a, pair<int, int> b) {
return a.first < b.first;
}
int main() {
int n, m;
cin >> n >> m;
pair<int, int> farmers[5000];
pair<int, int> x;
for (int i = 0; i < m; i++) {
cin >> x.first >> x.second;
farmers[i]=x;
}
sort(&farmers[0], &farmers[0] + m, cmp);
int num = 0, sum = 0;
for (int i = 0; i < m; i++) {
num += farmers[i].second;
sum += farmers[i].first * farmers[i].second;
if (num > n) {
sum -= farmers[i].first * (num - n);
break;
}
}
cout << sum;
return 0;
}
问题:有n个人在一个水龙头前排队接水,加入每个人接水的时间为 T i T_i Ti,找出这n个人排队的一种顺序,使得n个人的平均等待时间最小。
输入:第一行为一个整数n,第二行n个整数,第i个整数 T i T_i Ti表示第i个人的等待时间 T i T_i Ti
输出:两行,第一行为一种平均时间最短的排序顺序,第二行为这种排列方案下的平均等待时间(输出结果精确到效数点后两位)。
接水快的人先接水,通过局部最优解可以导出整体最优解。
#include <iostream>
#include <algorithm>
#include <iomanip>
using namespace std;
//这里需要加强健壮性~
//是true的时候,a在b的前面
bool cmp(pair<int, int>a, pair<int, int>b) {
if (a.second < b.second) return true;
if (a.second == b.second && a.first < b.first) return true;
else return false;
}
int main() {
int n;
pair<int, int> T[1000];
pair<int,int> x;
cin >> n;
for (int i = 1; i <= n; i++) {
x.first = i;
cin >> x.second;
T[i] = x;
}
sort(T+1, T + n+1,cmp);
long num = 0;
//这里设置为int时,最大的数据量通过不了
for (int i=1; i <= n; i++) {
cout << T[i].first << " ";
num += T[i].second * (n - i);
}
cout << endl;
cout <<fixed<< setprecision(2)<< num/10.0;
return 0;
}
1.小数输出问题,我还看了一下这个:cout输出怎么控制几位小数_C+±-输出:保留两位小数
2.如果数据量太大不能复制过来进行调试可以# i n c l u d e < f s t r e a m > include
3.输入数据时打错了,可以F5清空这一行(不过这个好像跟自己电脑也有关系,不一定行)