贪心法
思想:不断地找到当前最优的解。
题目一 硬币问题
题外话:这是一个不尽相异的组合问题,A={13,52,101,503,1000,5002},
组合数=11!/(3!*2!*1!*3!*0!*2!)
解题思路:尽可能的先选大面值的硬币,直到不能分的时候,开始分第二大面值的硬币,…,直至分完。
/*
input:
3 2 1 3 0 2 620
output:
6
*/
#include<iostream>
using namespace std;
// 硬币面值
const int C[6]={1,5,10,50,100,500};
// 硬币个数
int Cnt[6] ;
// 目标数值
int aim;
// 总共需要的硬币数
int count = 0;
int main(){
cout<<"依次输入六个面值硬币的个数和目标数值:"<<endl;
for(int i=0;i<6;i++){
cin>>Cnt[i];
}
cin>>aim;
for(int i=5;i>=0;i--) {
int t = min(aim/C[i],Cnt[i]); //使用第i个硬币的个数
aim -= t * C[i] ;
count+=t;
// 以下是我最初的解题,时间消耗不如上面的,代码也不够精简
// for(int j=0;j<Cnt[i];j++){
// if(aim>=C[i]) {
// cout<<C[i]<<endl;
// aim-=C[i];
// count++;
// }
// }
}
cout<<"需要的硬币数:"<<count<<endl;
return 0;
}
题目二 区间调度问题
要求是选择尽可能多的工作,基于贪心的想法,加上本题给出的例子,每次选取开始时间最早的工作,
但是对于开始的早的,结束时间很晚的,这种策略就是错的。如下图:
因此,要仔细思考一种合理的算法:
- 在可选的工作中,每次选取结束时间最早的
- 在可选的工作中,每次选取用时最短的
- 在可选的工作中,每次选取与最少工作有重叠的工作
算法2、3都有反例,算法1是正确的
/*
input:
5
1 2 4 6 8
3 5 7 9 10
*/
#include <iostream>
#include<algorithm>
using namespace std;
const int MAX_N = 100000;
// 事件数,开始时间,结束时间
int N,S[MAX_N],T[MAX_N];
// 用于对事件排序的pair数组
pair<int, int> itv[MAX_N];
void solve(){
// 将事件的起始、终止时间存入pair数组中
// pair:字典序排序
// 为了便于pair对 T 进行排序 ,将 T 存入first中
for(int i = 0; i < N; i++){
itv[i].first = T[i];
itv[i].second = S[i];
}
sort(itv, itv + N); //sort 中存入数组的物理首尾地址即可 ,调用algorithm
int ans = 0, t = 0;
for(int i=0;i<N;i++){
if(t<itv[i].second) {
ans++;
t = itv[i].first;
}
}
cout<<ans;
}
int main(){
cin>>N;
for(int i=0;i<N;i++){
cin>>S[i];
}
for(int i=0;i<N;i++){
cin>>T[i];
}
solve();
return 0;
}
友情link:pair详解 sort函数用法详解
对于pair方便将两个数据组合起来排序的说法,我想到了结构体,这里是我原来关于sort()的文章。sort()相关题目
题目三 字典序最小问题[/(ㄒoㄒ)/~~又是近两小时消耗]
Question:神马是字典序呢?
Answer:比较两个字符串,先比较第一个字符,如果不同,首字母小的整个字符串就是小的;如果相同,用相同的规则继续比较。就像咱们小时候查字典一样的嘛,a开头的单词就在b开头的单词前头,apple就在anaconda的后头。
规则:每次取 B 的开头或结尾放到 T 的后面
目标:T 前面的字符串尽量小就ok
考虑:每次取 B 的开头和结尾的最小元素放到 T 的末尾 ,应该可以得到很好的解,
但是当遇到两边元素相同的时候,如何做取舍呢? 我觉得这时应该前后指针为往里移一个,
判断此时的元素那个小,就选取哪边的元素放到 T
比如说,ACDBCB,第一次(A,B)中取A,第二次(B,C)中取B,第三次(C,C)中如果取了pre标记的C,下一次只能从(C,D)中取到C,而如果取了post标记的C,下一次就是从(C,B)中得到的B。所以此处遇到相同元素显然要先判断一下里面元素的相对大小。
以下程序是我写的烂包,把简单问题搞乱了。
考虑到元素相等时的取舍问题,思路是好的,往里判断是对的,但是不需要里面元素比较的结果,此次筛选只是为了知道遇到相同元素时,如何选取对下一次能选到最优的解有帮助,所以只需要判断完成标记即可,不需要用递归将pre+1,post-1传入计算。
/*
input:
6 ACDBCB
output:
ABCBCD
*/
#include<iostream>
#include<algorithm>
using namespace std;
const int MAX_N = 1000;
int N;
char S[MAX_N],T[MAX_N];
char T_value(int pre,int post) {
char temp = ' ';
int pre = 0, post = N-1;
if(pre <= post){
if(S[pre] > S[post]){
temp = S[post];
post -= 1;
}
if(S[pre] < S[post]) {
cout<<pre<<":"<<S[pre]<<" "<<post<<" :"<<S[post]<<endl;
temp = S[pre];
pre += 1;
}
if(S[pre] == S[post]){
temp = S[pre];
T_value(pre+1,post-1);
}
}
return temp;
}
int main(){
cin>>N;
for(int i=0;i<N;i++){
cin>>S[i];
}
for(int i=0;i<N;i++){
T[i] = T_value();
}
for(int i=0;i<N;i++){
cout<<T[i];
}
return 0;
}
这里是书上的程序,确实又精简又好用:
用left标记是否输出的是左边的元素。
在if判断中没有相等的情况,所以就进行循环,使pre和post同时向里移一位,继续比较,也不改变pre和post的大小。【下面的a,b就是我写的pre和post】
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAX_N = 1000;
int N;
char S[MAX_N];
void solve() {
int a = 0, b = N-1;
while(a <= b) {
bool left = false; // 此处标记是否取的是左边的元素
for(int i = 0; a + i <= b; i++) {
if(S[a + i] < S[b - i]) {
left = true;
break;
} else if(S[a + i] > S[b - i]) {
left = false;
break;
}
}
if(left)
putchar(S[a++]);
else
putchar(S[b--]);
}
}
int main(){
cin>>N;
for(int i = 0; i < N; i++){
cin>>S[i];
}
solve();
return 0;
}
题目四 Saruman’s Army(POJ 3069)
循环次数很明显是n次,将最左边的点标为s。
/*
Saruman's Army(POJ 3069)
input:
6 10
1 7 15 20 30 50
output:
3
*/
#include<iostream>
#include<algorithm>
using namespace std;
const int MAX_N = 100;
int n,r;
int a[MAX_N];
void solve(){
sort(a,a+n); // 将任意给定的点进行排序
int i = 0,ans = 0;
while (i < n) {
int s = a[i++]; //s记录unmarked最左边的点 s = 1
while (i < n && a[i] <= s+r) i++; // i = 1 一直向右边找距离大于r的点 i = 2
int p = a[i - 1] ; // p是新加上标记的点的位置 p = 7
while (i < n && a[i] <= p+r) i++; // i = 2 继续向右 i = 3
cout<<s<<" "<<p<<endl;
ans++;
}
cout<<ans;
}
int main(){
cin>>n>>r;
for (int i = 0; i < n; i++) {
cin>>a[i];
}
solve();
return 0;
}
题目五 Fence Repair(POJ 3253)
第一个思路:
【5、8、8】或者【8、5、8】,每次将最大的切下来,那剩下的再划分,最大的算价钱就只需要算一次了,因为越晚划分成独立的小块,随着次数的增加要算n次钱。
第一?:分成【8、5】和【8】,价钱P:8+5+8
第二?:【8】【5】,价钱P:P+8+5
所以,思路应该是先把想要的最大的独立块切下来,小的再从剩余的木料中慢慢切分。
换个例子【1,2,3,4,5】验证一下。
第一?:【1,2,3,4】,【5】,价钱P:15
第二?:【1,2,3】,【4】,价钱P:15+10
第三?:【1,2】,【3】,价钱P:15+10+6
第四?:【1】,【2】,价钱P:15+10+6+3 = 34
再仔细思考一下,每一次付钱只能切一刀,一刀只能得到两个小块,所以,每次切的时候都考虑最终需要的独立的小块正确吗?答案是不正确。比如上一个例子,花15块钱切第一刀,将15分成10(需要切割的)和5(是独立的),意味着再下一次要付10块钱来继续划分成别的小块,这不是很亏吗?相当于多算了好几次小块的钱。
所以,改变思路:
- 求出sum[L1,L2,…,Ln]
- 找出其中能将左右基本分均匀的点,也就是左右两边的和要接近sum/2
然后验证之后也是有误差的,求出来不是最优解,存在小块如何分成两个不同的组的问题,很小的块不会影响左右和的稳定,但是会影响下一步切割,所以参考了书上的思路。
书上的思路,将分割的方法用二叉树表示:
最终的总代价=Σ结点*层数=(3+4+5)*2+(1+2)*3 = 33
并且最小的两个结点应该是兄弟结点,位于最底层。
为什么3 4 成兄弟了呢?按照以上理论,不应该是(1+2)和3是兄弟吗?下面是我修改的图:
这是我的理解画的图,最终的总价格是一样的,计算结果没变。这个就是构建最小二叉树的思想。
/*
Fence Repair(POJ 3253)
input:
5
5 1 2 4 3
output:
33
*/
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAX_N = 100;
int n;
int a[MAX_N];
// 直到计算到木板为1块时为止
void solve(){
ll ans = 0;
while (n > 1) {
int s = 0, p = 1;
if (a[s] > a[p]) swap(s, p);
// s标记的最小,p标记的次小
for(int i=2;i<n;i++){
if(a[i]<a[s]){
p = s;
s = i;
}else if (a[i]<a[p]){
p = i;
}
}
cout<<s<<" "<<p<<endl;
int t = a[s] + a[p];
ans += t;
if(s == n-1) swap(s,p);
a[s]=t; // 把上一步求的和放到s位上
a[p] = a[n-1]; // 要把遗落在n-1位上的元素搬到p来
n--; // 然后数组小一位
}
cout<<ans;
}
int main(){
cin>>n;
for (int i = 0; i < n; i++) {
cin>>a[i];
}
solve();
return 0;
}