贪心
简单贪心
贪心法是求解一类最优化问题的方法,它总是考虑在当前状态下局部最优的策略,以使全局最优
思路是: 从问题的某个初始解出发一步一步地进行,根据某个优化测度每一步都要确保获得局部最优解. 如果下一个数据和部分最优解连在一起不再是可行解时, 就不把该数据添加到部分解中, 直到把所有数据枚举完, 或者不能再添加算法停止, 这样找到的第一个解通常就是最优的
PAT B1020月饼/PAT A1070Mooncake
题意
已知月饼需求量D
, n
种月饼各自的总售价和库存量, 问怎样销售这些月饼, 使总销售额达到最大
思路
总是选择单价最高的月饼出售
先计算出各种月饼的单价, 再按从大到小排序枚举, 直到需求量被满足
实现
#include<bits/stdc++.h>
using namespace::std;
struct moon
{
double n; //库存量
double price; //单价
double total; //总价
};
bool cmp(moon a, moon b)
{
return a.price > b.price; //使用sort后是由大到小排序
}
int main()
{
moon cake[1005];
int n;
double d, sum = 0;
cin >> n >> d;
for(int i = 1; i <= n; i++)
cin >> cake[i].n;
for(int i = 1; i <= n; i++)
cin >> cake[i].total;
for(int i = 1; i <= n; i++)
cake[i].price = cake[i].total / cake[i].n;
sort(cake + 1, cake + n + 1, cmp);
for(int i = 1; i <= n; ++i)
{
if(d >= cake[i].n)
{
sum += cake[i].total;
d -= cake[i].n;
}
else
{
sum += cake[i].price * d;
break;
}
}
printf("%.2f", sum);
return 0;
}
PAT B1023组个最小数
思路
先从1~9中选出一个不为0的最小数输出, 再从小到大依次输出各个数字
实现
#include <bits/stdc++.h>
int main()
{
int count[15];
for(int i = 0; i <= 9; i++)
cin >> count[i];
for(int i = 1; i <= 9; i++)
{
if(count[i] != 0)
{
cout << i;
count[i]--;
break;
}
}
for(int i = 0; i <= 9; ++i)
for(int j = 1; j <= count[i]; ++j)
cout << i;
return 0;
}
LanQiao基础训练-完美的代价
思路
首先判断Impossible的情况
回文串, 需要左右对称, 只有最中间的那个字符不需要有和它相同的字符对应(当然,这是串长为奇数的情况), 这说明奇数个的字符种类 > 1就必无法构成回文串, 例如abbbaa
下面进行字母交换 (注意只有相邻的字母才能交换)
保证最小次数的策略: 每个字母都单向移动 (不做无用功), 从外向内进行交换, 内部的交换不会影响外部, 不易产生重复
在串中点的左边枚举一个字符, 在该字符的右边找一个和它相同的字符完成交换(由外向内查找), 记录次数, 单个找不到相同字母的字符不进行交换, 直接计算其到达中点需要走的步数即可
实现
#include <bits/stdc++.h>
using namespace::std;
char str[9000];
int st[30];
int main()
{
int n;
cin >> n;
cin >> str + 1;
//判断Impossible的情况
for(int i = 1; i <= n; ++i)
{
int temp = str[i] - 96;
st[temp]++;
}
int cnt = 0;
for(int i = 1; i <= 26; ++i)
{
if(st[i] % 2 != 0)
cnt++;
}
if(cnt > 1)
{
cout << "Impossible";
return 0;
}
//通过交换构成回文
int pos = n; //需要构成回文的位置
int ans = 0; //计数
for(int i = 1; i < (n + 1) / 2; ++i)
{
if(str[i] == str[pos])
{
pos--; //该位置可以直接构成回文,需要构成回文的位置向里走
continue;
}
else //不能构成回文,需要进行交换
{
int j;
for(j = pos - 1; j > i; --j) //查找相同的字母
{
if(str[j] == str[i])
{
while(j < pos)
{
char temp = str[j];
str[j] = str[j + 1];
str[j + 1] = temp;
ans++;
j++;
}
pos--; //通过交换构成了回文,需要构成回文的位置向里走
break; //已构成回文,停止查找
}
}
if(i == j) //没有找到相同的字符
{
ans += (n + 1) / 2 - i; //单个的字母走到中间需要交换的次数
}
}
}
cout << ans;
return 0;
}
区间贪心
引例: 区间不相交问题
给出n
个开区间(x, y)
, 从中选择尽量多的开区间, 使得这些开区间两两没有交集
例如对于开区间(1, 3) (2, 4) (3, 5) (6, 7)
, 选出最多三个区间(1, 3) (3, 5) (6, 7)
思路
- 区间A为区间B包含的情况, 例如
A = (1, 9)
和B = (3, 4)
, 此时, 显然选择B, 更有利于容纳更多区间 - 接下来将所有开区间按左端点从大到小排列, (设区间A1的左端点为x1, 右端点为y1, 其余区间同理), 排除包含区间的情况后, 必有
y1 > y2 > ... > yn
, 如果将A1的右端和A2不重合部分去掉, 那么A1就会被A2包含, 根据1中的结论, 应当选择A1, 推而广之, 也就是我们总是选左边端点大的区间
实现
#include<bits/stdc++.h>
using namespace::std;
struct Interval //区间
{
int x; //左端点
int y; //右端点
};
bool cmp(Interval a1, Interval a2)
{
return a1.x >= a2.x;
}
int main()
{
int n, ans = 1;
cin >> n;
Interval a[1000];
for(int i = 1; i <= n; ++i)
{
cin >> a[i].x >> a[i].y;
}
sort(a + 1, a + n + 1, cmp);
prex = a[1].x;
for(int i = 2; i <= n; ++i)
{
if(a[i].y <= prex)
{
ans++;
prex = a[i].x;
}
}
cout << ans;
return 0;
}
显然, 贪心算法适用于全局的最优解可以由子问题的最优解得出的问题