Bootstrap

ACM/ICPC算法基础训练教程(4)

1.4.1 基本概念

贪心策略是指从问题的初始状态出发,通过若干次的贪心选择而得出最优值(或较优解)的一种解题方法。
其实,从“贪心策略”一词可以看出,贪心策略总是做出在当前看来是最优的选择。也就是说,贪心策略并不是从整体上考虑问题,它所做出的选择只是在某种意义上的局部最优解,而许多问题自身的特性决定了该题运用贪心策略可以得到最优解或较优解例如:在n行m列的正整数矩阵中,要求从每一行中选一个数,使得选出的n个数的和最大。
本题可用贪心策略求解。选n次,每一次选相应行中的最大值即可。
再如:设定有n台处理机p,p,…,和m个作业j,j,…,j,处理机可并行工作,作业未完成不能中断,作业j;在处理机上的处理时间为t;,求解最佳方案,使得完成m项工作的时间最短。
本题不能用贪心算法求解。理由是:若n=3,m=6,则6个作业的时间分别是 11、7、5、5、4、7,用贪心策略(每次将作业加到最先空闲的机器上)时间为 15,而用搜索策略最优时间应是 14,但是贪心策略提供了一个线索,那就是每台处理上的时间不超过 15,为搜索提供了方便。
总之,贪心算法不能保证求得的最后解是最佳的解,一般用来求某些最大或最小解的问题,能确定某些问题的可行解的范围,特别是给搜索算法提供了依据。
贪心算法的特点如下。
(1)贪心选择性质:所谓贪心选择性质是指应用同一规划,将原问题变为一个相似的但规模更小的子问题,而后的每一步都是当前看似最佳的选择。这种选择依赖于已做出的选择,但不依赖于未做出的选择。从全局来看,运用贪心策略解决的问题在程序的运行过程中无回溯过程。关于贪心选择性质,读者可在后续给出的贪心策略状态空间图中得到深刻的体会。
(2)局部最优解:局部最优解是贪心策略的数学描述。虽然运用贪心策略解题在每一次都取得了最优解,但是能够保证局部最优解的不只是贪心算法。例如,动态规划算法就可以满足局部最优解,但贪心策略比动态规划算法效率更高,占用内存更少,编写程序更简单。

1.4.2 例题讲解

【例1-8】Tian Ji-The Horse Racing.
田忌和齐王赛马,胜一场可以获得200金,负一场损失200金,平局无得无失。现在给出马的数量,田忌的每匹马的速度和齐王的每匹马的速度。求出田忌最多可以赢得多少金。
题目描述:
输人包含多组数据。每组数据的第一行为一个正整数n(n<1000),表示每一方有多少匹马,第二行为n个整数表示田忌每匹马的速度,第三行n个整数表示齐王每匹马的速度。当n为0时表示输人数据结束。每组样例输出一行,给出田忌可以赢得的具体金数
输入样例:
3
92 83 71
95 87 74
2
20 20
20 20
2
20 19
22 18
0
输出样例:
200
0
0
题目来源:
http://acm. tju. edu.cn/toj/showp.php?pid=1188
参考代码:

#include<iostream>
using namespace std;
int n;
int a[1000], b[1000];
int main() {
	while (cin >> n && n) { //输入每一方有多少匹马
		for (int i = 0; i < n; i++) {
			cin >> a[i];  //田忌每匹马的速度
		}
		for (int i = 0; i < n; i++) {
			cin >> b[i]; //齐王每匹马的速度
		}
		int t, ans = 0;
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < n - 1; j++) {
				if (a[j] < a[j + 1]) {
					swap(a[j], a[j + 1]);
				}
				if (b[j] < b[j + 1]) {
					swap(b[j], b[j + 1]);
				}
			}
		}	
	int l1 = 0, l2 = 0, r1 = n - 1, r2 = n - 1;//表示田忌(1)和齐王(2)最差的马(r)与最好的马(1)的下标
	while (l1 <= r1) {
		if (a[l1] > b[l2]) {
			ans += 200;
			l1++;
			l2++;
		}
		else if (a[l1] < b[l2]) {
			ans -= 200;
			r1--;
			l2++;
		}
		else if (a[l1] == b[l2] && a[r1] > b[r2]) {
			ans += 200;
			r1--;
			r2--;
		}
		else if (a[l1] == b[l2] && a[r1] <= b[r2]) {
			if (a[r1] < b[l2]) ans -= 200;
			r1--;
			l2++;
		}
	}
	cout << ans << endl;
	}
	return 0;
}

【例1-9】Wooden Sticks。
题目描述:
有许多木桩需要用机器处理,每个木桩有一个长度和重量w:。给机器安装第一个木桩时需要1分钟,之后每次换木桩时,若新木桩的长度<!,重量w<w;,则不需耗时;否则,重新安装,耗时1分钟。现在给出t组数据,每组有n个木桩,分别给出长度及重量。求每组木桩最小的总安装时间。
输人的第一行为一个正整数T,表示数据共T组。每组数据的第一行为一个正整数n(1≤n<5000),表示该组样例有n个木桩,第二行为2Xn个整数,每两个数表示一根木桩的长度与重量。输出包含一行,表示出每组数据的最小安装时间。输入样例:
3
5
4 9 5 2 2 1 3 5 1 4
2 2 1 1 2 2
3
1 3 2 2 3 1
输出样例:
2
1
3
题目来源:
http://acm.tju.edu.cn/toj/showp.php?pid=1469
对品不木桩按双标准排序(先满足长度,再满足重量),之后贪心策略为每次找出一解木桩,使组内满足L<,且w<!,这样组内换木桩就不需要重新安装,剩下的木桩制进行寻找,直到所有木桩都属于某个组。
代码:

#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
//描述木桩的类
class Node {
public:
int x, y;
Node(){}
//重载小于号,便于排序K0-大搬等bool operator<(const Node n)const {if(x==n.x)return y<n.y;
return x<n.X;
Node node[5005];
int cases,n,flag[5005];
int main()
U)日人
//输入样例数scanf("号d",&cases);while(cases--){
scanf("d"&n);
//输入木桩数
//每个木桩的长度与重量scanf("各d号d",&node[i].x,&node[i].y);sort(node,nodetn);//升序排序//表示某个木桩是否属于某个组,0表示没有memset(flag,0,sizeof(flag)):int ans=0;
for(int i=0;i<n; i++)
for(int i=0;i<n;i++){
if(flag[i]==0){
//该木桩暂时不属于某个组
int p=node[i].y;for(int j=i;j<n;j++){ //尽量找到更多的可以归为同一组的木桩if(flag[j]==0 && node[j].y>=p){
p=node[j].y;
flag[j]=1/
ans++;
printf("号d\n",ans);
//找到一个新的组,总时间增加
//输出结果
return 0;
;