算法分析与设计学习笔记---6

贪心题目二
ZOJ1025-Wooden Sticks
现有n根木棒,已知它们的长度和重量。要用一部木工机一根一根地加工这些木棒。该机器在加工过程中需要一定的准备时间,是用于清洗机器,调整工具和模版的。
木工机需要的准备时间如下:
(1)第一根木棒需要1min的准备时间;
(2)在加工了一根长为l ,重为w的木棒之后,接着加工一根长为l ’ (l ≤ l’ ),重为 w’ ( w≤w’)的木棒是不需要任何准备时间的,否则需要一分钟的准备时间。
给定n根木棒,找到最少的准备时间。
例如现在有长和重分别为(4,9),(5,2),(2,1),(3,5)和(1,4)的五根木棒,那么所需准备时间最少为2min,顺序为(1,4),(3,5),(4,9),(2,1),(5,2)。

输入
输入有多组测试例。输入数据的第一行是测试例的个数T。
每个测试例两行:第一行是一个整数n(1≤n≤5000),表示有多少根木棒;第二行包括n×2个整数,表示l1,w1,l2,w2,l3,w3,…,ln,wn,其中li和wi表示第i根木棒的长度和重量。数据由一个或多个空格分隔。
输出
输出是以分钟为单位的最少准备时间,一行一个。

本题仅仅使用贪心算法是不够的,排序之后还要使用动态规划的算法。
(1)数据结构
采用结构体表示木棒的信息:
#define maxN 5001
struct stick
{
int l; //木棒的长度
int w; //木棒的重量
};
stick data[maxN]; //存放所有木棒

(2)按木棒的长度使用贪心算法
利用C++的标准模板库函数sort()实现排序:
sort(data, data+n, cmp);
排序函数cmp()的实现:
int cmp(stick a, stick b)
{
//长度相等时,按重量排序
if (a.l == b.l) return a.w < b.w;
//优先按长度排序
else if (a.l < b.l) return true;
return false;
}

(3)使用动态规划的方法,计算重量w的最长单调递增子序列的个数
用数组b记录重量w的分组序号。
在表中,4,5和9的组序号是1,1和2的组序号是2。
则a[i].w(0≤i<n)最长递增子序列的分组个数为:max {b[i]}。
b[i]满足最优子结构性质,可以递归地定义为:
b[0]=1;
b[i] = max {b[j]}+1,0≤j<i
算法分析与设计学习笔记---6

//形参n是木棒的数量,stick是木棒参数的数组
int LIS(int n, stick a[])
{
  //数组b表示木棒分组的序号
  int b[maxN];
  memset(b, 0, sizeof(b));
  int i, j, k;
  b[0]=1;
  for (i=1; i<n; i++)
  {
    //计算第i个木棒的的分组序号
    k=0;
    for (j=0; j<i; j++)
      if (a[i].w<a[j].w && k<b[j]) k=b[j];
    b[i]=k+1;
  }
  //查找最大的分组序号(数组b中的最大值)
  int max=0;
  for (i=0; i<n; i++)
    if (b[i]>max) max=b[i];
  return max;
}
ZOJ1161-Gone Fishing钓鱼
约翰有h(1≤h≤16)个小时的时间,在该地区有n(2≤n≤25)个湖,这些湖刚好分布在一条路线上,该路线是单向的。约翰从湖1出发,他可以在任一个湖结束钓鱼。但他只能从一个湖到达另一个与之相邻的湖,而且不必每个湖都停留。
假设湖i(i=1~n—1),以5分钟为单位,从湖i到湖i+1需要的时间用ti(0<ti≤192)表示。例如t3=4,是指从湖3到湖4需要花20分钟时间。
已知在最初5分钟,湖i预计钓到鱼的数量为fi(fi≥0)。以后每隔5分钟,预计钓到鱼的数量将以常数di(di≥0)递减。如果某个时段预计钓到鱼的数量小于或等于di,那么在下一时段将钓不到鱼。为简单起见,假设没有其它的钓鱼者影响约翰的钓鱼数量。
编写程序,帮助约翰制定钓鱼旅行的计划,以便尽可能多的钓到鱼。
算法分析与设计学习笔记---6
输入
对每组测试例,第一行是n,接下来一行是h。下面一行是n个整数fi(1≤i≤n),然后是一行n个整数di(1≤i≤n),最后一行是n—1个整数ti(1≤i≤n—1)。
输入样例
输出
对每个测试例,输出在每个湖上花费的时间,这是约翰要实现钓到最多的鱼的计划(必须使整个计划在同一行输出)。
接下来一行是钓到的鱼的数量:
如果存在很多方案,尽可能选择在湖1钓鱼所耗费的时间,即使有些时段没有钓到鱼;如果还是无法区分,那就尽可能选择在湖2钓鱼所耗费的时间,以此类推。

(1)数据结构
每个湖预计钓到鱼的数量,定义为数组:
#define NUM 30
int f[NUM];
每个湖预计钓到鱼的数量的递减值,定义为数组:
int d[NUM];
相邻湖之间的旅行时间,定义为数组:
int t[NUM];
钓鱼计划,定义为数组:
int plan[NUM];
湖的个数n,用于钓鱼的时间h,尽可能多的钓鱼数量best。

(2)搜索,在任意一个湖结束钓鱼时的最优钓鱼计划
首先把用于钓鱼的时间h,由小时转换为以5分钟为单位的时间:h=h×60/5;
这样把钓5分钟鱼的时间称为钓一次鱼。由于约翰从湖1出发,可以在任一个湖结束钓鱼,要得到最优解,就需要进行搜索。

算法分析与设计学习笔记---6
(3)采用贪心策略,每次选择鱼最多的湖钓一次鱼
对于每个湖来说,由于在任何时候鱼的数目只和约翰在该湖里钓鱼的次数有关,和钓鱼的总次数无关,所以这个策略是最优的。一共可以钓鱼time次,每次在n个湖中选择鱼最多的一个湖钓鱼。
采用贪心算法构造约翰的钓鱼计划。
可以认为约翰能从一个湖“瞬间转移”到另一个湖,即在任意一个时刻都可以从湖1到湖pos中任选一个钓一次鱼。

选择鱼最多的湖钓鱼的贪心算法实现
(3)采用贪心策略,每次选择鱼最多的湖钓一次鱼
对于每个湖来说,由于在任何时候鱼的数目只和约翰在该湖里钓鱼的次数有关,和钓鱼的总次数无关,所以这个策略是最优的。一共可以钓鱼time次,每次在n个湖中选择鱼最多的一个湖钓鱼。
采用贪心算法构造约翰的钓鱼计划。
可以认为约翰能从一个湖“瞬间转移”到另一个湖,即在任意一个时刻都可以从湖1到湖pos中任选一个钓一次鱼。

//从湖1起到湖pos止,花费时间time(不含路程)的钓鱼计划
void greedy(int pos, int time)
{
  if (time <= 0) return;      //时间已经用完
  int i, j;
  int fish[MAXN];
  int p[MAXN];
  int t = 0;
  for (i = 0; i < pos; ++i)
    fish[i] = f[i];
  memset(p, 0, sizeof§);
  ……
}

//在时间time内,选择鱼最多的湖钓鱼;如果鱼都没有了,就把时间放在湖1上
for (i = 0; i < time; ++i)
{
  int max = 0; //鱼最多的湖中,鱼的数量
  int id = -1;     //鱼最多的湖的编号
  //查找鱼最多的湖中,鱼的数量和湖的编号
  for (j = 0; j < pos; ++j)
    if (fish[j] > max){
      max = fish[j];
      id = j;
    }
  if (id != -1)      //找到了,进行钓鱼处理
  {
    ++p[id];
    fish[id] -= d[id];
    t += max;
  }
  //没有找到(从湖1起到湖pos全部钓完了),就把时间放在湖1上
  else ++p[0];
}

//处理最优方案
if (t > best)
{
  best = t;         //最优值
  memset(plan, 0, sizeof(plan));
  for (i = 0; i < pos; ++i)  //最优解
    plan[i] = p[i];
}

输出钓鱼计划时,再把5乘回去,就变成实际的钓鱼时间(分钟):
for (i=0; i<n-1; ++i)
printf("%d, “, plan[i] * 5);
printf(”%d\n", plan[n-1] * 5);
printf(“Number of fish expected: %d\n”, best);