贪心算法初练

贪心算法由顾名思义,就是不断根据某种策略来追求最优策略的算法设计方法.当然,最好的对贪心算法的理解还是刷题,从水题刷起,了解贪心算法的注意事项和方法.

硬币问题:

有1元,5元,10元,100元,500元的硬币各C1,C5,C10,C100,C500枚.现在要用这些硬币来支付A元,最少需要几枚硬币?嘉定每题至少存在一种支付方案.

我们从人的思维与考虑这个问题,那我们显而易见肯定优先选取小于所求数额中面值最大的硬币,这就是在满足条件下的最优策略.贪心算法遵循某种鬼泽,不断地选取当前最优策略.

下面贴出代码:

/*************************************************************************
    > File Name: coin_problem.c
    > Author: zhanghaoran
    > Mail: chilumanxi@gmail.com 
    > Created Time: 2015年06月16日 星期二 10时28分20秒
 ************************************************************************/

#include <stdio.h>
#include <string.h>


const int v[6] ={1, 5, 10, 50, 100, 500};
int num[6];
int A;

int min(int a, int b){
	return a < b ? a : b;
}

void solve(){
	int i, t;
	int ans = 0;
	for(i = 5; i >= 0; i --){
		t = min(A / v[i], num[i]);
		A -= t * v[i];
		ans += t;
	}
	printf("%d
", ans);
}

int main(void){
	int i;
	for(i = 0; i < 6; i ++){
		scanf("%d", &num[i]);
	}
	scanf("%d", &A);
	solve();
}

好的,这个题算是入门了,那我们会发现,导致贪心算法正确与否的关键,就是所谓的最优策略的选取,这是非常重要的,因为贪心算法一直遵循这个规律.看下面这个例子:

区间调度问题:

有n项工作,每项工作分别在si时间开始,在ti时间解数,对于每项工作,你都可以选择参与与否.如果选择了参与,那么自始至终都必须全程参与,此外,参与工作的时间不能重叠,即使是开始的瞬间和结束的瞬间也是不允许的.要求尽可能多的参与工作,问最多能参与多少项工作?


针对这个问题我们如何选取最优策略呢?我们可能会想到,每次选取开始时间最早的,显然这是错的,比方说有一个工作第一个开始,从始至终,这显然不合理.那么我们或许可以选择与别的工作重叠最少的,但是这也存在问题,比方说有一个工作虽然重叠少但是耗费时间长,在这短时间内同样有比他重叠多且耗费时减少的两个工作.那么或许我们可以用用时短的作为选取?显然也不可以,比方说两个长工作中间插入一个短工作且与两个长工作相交,这显然也是错误的

最后我们选取的策略是结束时间最早的工作,这样的选择是正确的.代码如下:

我们所做的只需要进行升序排序结束时间的数组,然后进行计算即可

/*************************************************************************
    > File Name: 区间调度问题.cpp
    > Author: zhanghaoran
    > Mail: chilumanxi@gmail.com
    > Created Time: 2015年06月16日 星期二 10时55分54秒
 ************************************************************************/

#include <iostream>
#include <algorithm>
#include <utility>
#define MAX_N 100000

using namespace std;

void solve();

int N, S[MAX_N], T[MAX_N];

pair<int, int> itv[MAX_N];

void solve(){
	for(int i  = 0; i < N; i ++){
		itv[i].first = T[i];
		itv[i].second = S[i];
	}
	sort(itv, itv + N);

	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(void){
	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;
}

现在来看两道POJ的题目

Best Cow Line
Time Limit: 1000MS   Memory Limit: 65536K
Total Submissions: 12937   Accepted: 3739

Description

FJ is about to take his N (1 ≤ N ≤ 2,000) cows to the annual"Farmer of the Year" competition. In this contest every farmer arranges his cows in a line and herds them past the judges.

The contest organizers adopted a new registration scheme this year: simply register the initial letter of every cow in the order they will appear (i.e., If FJ takes Bessie, Sylvia, and Dora in that order he just registers BSD). After the registration phase ends, every group is judged in increasing lexicographic order according to the string of the initials of the cows' names.

FJ is very busy this year and has to hurry back to his farm, so he wants to be judged as early as possible. He decides to rearrange his cows, who have already lined up, before registering them.

FJ marks a location for a new line of the competing cows. He then proceeds to marshal the cows from the old line to the new one by repeatedly sending either the first or last cow in the (remainder of the) original line to the end of the new line. When he's finished, FJ takes his cows for registration in this new order.

Given the initial order of his cows, determine the least lexicographic string of initials he can make this way.

Input

* Line 1: A single integer: N
* Lines 2..N+1: Line i+1 contains a single initial ('A'..'Z') of the cow in the ith position in the original line

Output

The least lexicographic string he can make. Every line (except perhaps the last one) contains the initials of 80 cows ('A'..'Z') in the new line.

Sample Input

6
A
C
D
B
C
B

Sample Output

ABCBCD

这个题目我们需要依次比较S串的头和尾,优先选择更靠前的字母,如果一样就继续深入.满80个以后换行,难度不大.

/*************************************************************************
    > File Name: Best_Cow_Line.cpp
    > Author: zhanghaoran
    > Mail: chilumanxi@gmail.com
    > Created Time: 2015年06月16日 星期二 11时28分45秒
 ************************************************************************/

#include <iostream>
#include <algorithm>
#include <utility>
#include <cstdio>
#define MAX_N 2001

using namespace std;

int N;
char S[MAX_N + 1];

void solve(){
	int a = 0, b = N - 1;
	int count = 0;
	while(a <= b){
		bool left = false;
		for(int i = 0; a + i <= b; i ++){
			if(S[a + i] < S[b - i]){
				left = true;
				count ++;
				break;
			}
			else if(S[a + i] > S[b - i]){
                left = false;
                count ++;
                break;
			}
		}
		if(left == true)
			putchar(S[a ++]);
		else
			putchar(S[b --]);
        if(count % 80 == 0)
            cout << endl;
		}
		cout << endl;
}

int main(void){
	cin >> N;
	for(int i = 0; i < N; i ++)
		cin >> S[i];
	solve();
}


对这个题目推而广之可以知道字典序比较类的问题经常用得上贪心算法.


Saruman's Army
Time Limit: 1000MS   Memory Limit: 65536K
Total Submissions: 5132   Accepted: 2653

Description

Saruman the White must lead his army along a straight path from Isengard to Helm’s Deep. To keep track of his forces, Saruman distributes seeing stones, known as palantirs, among the troops. Each palantir has a maximum effective range of R units, and must be carried by some troop in the army (i.e., palantirs are not allowed to “free float” in mid-air). Help Saruman take control of Middle Earth by determining the minimum number of palantirs needed for Saruman to ensure that each of his minions is within R units of some palantir.

Input

The input test file will contain multiple cases. Each test case begins with a single line containing an integer R, the maximum effective range of all palantirs (where 0 ≤ R ≤ 1000), and an integer n, the number of troops in Saruman’s army (where 1 ≤ n ≤ 1000). The next line contains n integers, indicating the positions x1, …, xn of each troop (where 0 ≤ xi ≤ 1000). The end-of-file is marked by a test case with R = n = −1.

Output

For each test case, print a single integer indicating the minimum number of palantirs needed.

Sample Input

0 3
10 20 20
10 7
70 30 1 7 15 20 50
-1 -1

Sample Output

2
4

这个题大意就是用最少的点,在指定的半径下去覆盖全部的点.

这个题我们要做的就是从第一个点开始,以其作为半径的最左面,然后放置第一个点,以此类推将所有的点包括在每一个半径内.当然,已经覆盖了的就不用再讨论了.

/*************************************************************************
    > File Name: Saruman_Army.cpp
    > Author: zhanghaoran
    > Mail: chilumanxi@gmail.com 
    > Created Time: 2015年06月16日 星期二 16时50分09秒
 ************************************************************************/

#include <iostream>
#include <algorithm>
#include <utility>
#include <cstring>
#define MAX_N 1001

using namespace std;

int N, R;
int pos[MAX_N];

void solve(){
	sort(pos, pos + N);
	int i = 0, ans = 0;
	while(i < N){
		int s = pos[i ++];
		while(i < N && pos[i] <= s + R) 
			i ++;
		int p = pos[i - 1];
		while(i < N && pos[i] <= p + R)
			i ++;
		ans ++;
	}
	cout << ans << endl;
}

int main(void){
	while(1){
		cin >> R;
		cin >> N;
		if(R == -1 && N == -1)
			break;
		for(int i = 0; i < N; i ++){
			cin >> pos[i];
		}
		solve();
	}
}

Fence Repair
Time Limit: 2000MS   Memory Limit: 65536K
Total Submissions: 30383   Accepted: 9828

Description

Farmer John wants to repair a small length of the fence around the pasture. He measures the fence and finds that he needs N (1 ≤N ≤ 20,000) planks of wood, each having some integer length Li (1 ≤ Li ≤ 50,000) units. He then purchases a single long board just long enough to saw into the N planks (i.e., whose length is the sum of the lengths Li). FJ is ignoring the "kerf", the extra length lost to sawdust when a sawcut is made; you should ignore it, too.

FJ sadly realizes that he doesn't own a saw with which to cut the wood, so he mosies over to Farmer Don's Farm with this long board and politely asks if he may borrow a saw.

Farmer Don, a closet capitalist, doesn't lend FJ a saw but instead offers to charge Farmer John for each of the N-1 cuts in the plank. The charge to cut a piece of wood is exactly equal to its length. Cutting a plank of length 21 costs 21 cents.

Farmer Don then lets Farmer John decide the order and locations to cut the plank. Help Farmer John determine the minimum amount of money he can spend to create the N planks. FJ knows that he can cut the board in various different orders which will result in different charges since the resulting intermediate planks are of different lengths.

Input

Line 1: One integer N, the number of planks 
Lines 2..N+1: Each line contains a single integer describing the length of a needed plank

Output

Line 1: One integer: the minimum amount of money he must spend to make N-1 cuts

Sample Input

3
8
5
8

Sample Output

34
这个题看起来是比较麻烦的,大意就是将木板切成N块,每切一次就会消耗当前长度的开销,最后切成给出的样子.

这个题我们用贪心算法考虑就是我们需要每次都选取最小的两块木板,然后将其拼接在一起,然后继续讨论,将拼接好的放在原最小数字的位置,然后将次小数字抛弃.反复即可.这个贪心算法我们选取的策略就是不断地选取最小的两个数然后进行拼接.

代码如下:

/*************************************************************************
    > File Name: Fence_Repair.cpp
    > Author: zhanghaoran
    > Mail: chilumanxi@gmail.com
    > Created Time: 2015年06月16日 星期二 17时43分48秒
 ************************************************************************/

#include <iostream>
#include <algorithm>
#include <utility>
#define MAX_N 200001
using namespace std;

typedef long long ll;

int N, L[MAX_N];

void solve(){
	ll ans = 0;
	while(N > 1){
		int minest = 0;
		int minles = 1;
		if(L[minest] > L[minles])
			swap(minest, minles);
		for(int i = 2; i < N; i ++){
			if(L[i] < L[minest]){
				minles = minest;
				minest = i;
			}
			else if(L[i] < L[minles]){
				minles = i;
			}
		}

		int t = L[minest] + L[minles];
		ans += t;

		if(minest == N - 1)
			swap(minest, minles);
		L[minest] = t;
		L[minles] = L[N - 1];
		N --;
	}
	cout << ans << endl;
}


int main(void){
	cin >> N;
	for(int i = 0; i < N; i ++){
		cin >> L[i];
	}
	solve();
	return 0;
}

贪心算法的初练大概就这么多,对于贪心算法的应用,如果我们能够选择正确的策略,我们就会得到非常高效的解答.


原文地址:https://www.cnblogs.com/chilumanxi/p/5136130.html