下面我们通过解决POJ题库中的几道应用贪心法思想编写程序的例题,进一步体会贪心法的应用。
【例1】产品包装。
问题描述
某工厂生产的产品高度均为h,截面尺寸分为1*1、2*2、3*3、4*4、5*5、6*6。工厂收到客户订单后,将订单中的产品用与产品高度h相同,尺寸为6*6的包装盒包装后寄给客户。
问最少要多少个包装盒?
输入
输入每行指定一个订单。每个订单由六个整数描述,由一个空格分隔,依次表示从最小大小1*1到最大大小6*6的各个规格的产品的数量。输入的结尾由包含六个零的行表示。
输出
输出文件为输入文件中的每一行包含一行。此行包含可将输入文件对应行中的订单打包到其中的最小数量的包装盒数。
输入样例
0 0 4 0 0 1
7 5 1 0 0 0
0 0 0 0 0 0
输出样例
2
1
(1)编程思路。
贪心策略是优先从最大规格的产品需要的包装盒数来考虑,因为一个包装盒在装完较大规格的产品后,该包装盒可能存在剩余空间,仍应该利用。
显然,6*6、5*5、4*4规格的每一个产品都需要一个包装盒,3*3规格的产品每四个装满一个包装盒,然后将剩余2*2和1*1规格的产品依次放入之前包装盒的空余部分。
6*6规格的产品需要一个包装盒且空间全占满,无剩余;5*5规格的产品需要一个包装盒,该包装盒还可以装11个1*1规格的产品;4*4规格的产品需要一个包装盒,该包装盒还可以最多装5个2*2规格或20个1*1规格的产品。但该包装盒剩余空间应优先考虑装2*2规格的产品。
对于3*3规格的产品,一个包装盒可以装1到4个3*3规格的产品,剩下的空间同样优先考虑装2*2规格的产品。简单在纸上画一下可得知,一个包装盒分别装1、2、3、4个3*3规格的产品后,剩余空间最多可以装5、3、1、0个2*2规格的产品。
剩下的2*2规格和1*1规格的产品就首先放进前面各较大规格产品所装包装盒剩余的空间中,不够的话启用新包装盒。
(2)源程序。
#include <stdio.h>
int main()
{
while (1)
{
int b1,b2,b3,b4,b5,b6;
scanf("%d%d%d%d%d%d",&b1,&b2,&b3,&b4,&b5,&b6);
if (b1+b2+b3+b4+b5+b6==0) break;
int ans,x1,x2;
ans=b6+b5+b4+(b3+3)/4;
x2=b4*5; // 装一个规格4产品的箱子还可装5个规格2的产品
switch (b3%4)
{
case 1: x2+=5; break;
case 2: x2+=3; break;
case 3: x2++;
}
if (x2<b2) // 为规格2剩余的空间不够装b2个产品
ans=ans+(b2-x2+8)/9;
x1=36*ans-36*b6-25*b5-16*b4-9*b3-4*b2;
if (x1<b1) // 为规格1剩余的空间不够装b1个产品
ans=ans+(b1-x1+35)/36;
printf("%d\n",ans);
}
return 0;
}
注:将此源程序提交给POJ 1017 Packets(http://poj.org/problem?id=1017)可以Accepted。
【例2】1的个数相同。
问题描述
给定一个大于0的整数n,把它转换为二进制,则其二进制数中至少有1位是“1”。编写一个程序,找出比给定的整数n大的最小整数m。要求m和n两个整数转换成二进制数后,二进制数中包含的1的个数相同。
例如,120的二进制数为01111000,则比120大且二进制数中1的个数相同的最小整数为135(10000111)。
输入
每行一个整数n(1 <= n<= 1000000),n=0表示输入结束。
输出
每行一个整数,为比n大的最小整数m,且m、n所对应的二进制数中1的个数相同。
输入样例
1
2
3
4
78
0
输出样例
2
4
5
8
83
(1)编程思路。
对十进制数n转化成的二进制数直接进行位变换,求出最小的整数m。
贪心方法是:先找到整数n对应的二进制数的最右边的1个1,从这个1开始,从右向左将连续出现的k个1变为0后,高1位的0变为1,再从最低位开始,将k-1个0变为1,即可得到最小的数n。
例如,32 对应的二进制数为00100000,将最右边的连续1个1变为0,高1位0变为1,即为01000000,对应整数为64。
又如,92 对应的二进制数为 01011100,将最右边的连续3个1变为0(得01000000),高1位变为1(得01100000),再将最低位的2(3-1)个0变为1,即为01100011,对应整数为99。
(2)源程序。
#include <stdio.h>
int main()
{
int n;
while (scanf("%d",&n) && n!=0)
{
int a,b,k,m;
for (a=0; (n & (1<<a))==0; a++) ; // 找到最右边的1个1所在位置a
for (b=a; (n & (1<<b))!=0; b++) ; // 找到从a位开始向左的连续个1
m =n | (1<<b); // 把b位改成1
for (k=a; k<b; k++) m^=(1<<k); // 将从a位到b-1位的1全部取反变为0
for (k=0; k<b-a-1; k++) m |= 1<<k; // 将最低的b-a-1个位的0变为1
printf("%d\n",m);
}
return 0;
}
注:将此源程序提交给POJ 2453 An Easy Problem(http://poj.org/problem?id=2453)可以Accepted。
【例3】构造字符串。
问题描述
有N个大写字母顺序排成一行,现要构造一个长度为N的字符串T。构造方法为:
1)从行首取出一个字母,加到T的尾部
2)从行尾取出一个字母,加到T的尾部
问用这种方法构造的字典序最小的字符串是什么?
输入
第1行:单个整数N
第2..N+1行:第i+1行包含原始行中第i个位置处字母('A'…'Z')
输出
能构造的最小的字典字符串。
输入样例
6
A
C
D
B
C
B
输出样例
ABCBCD
(1)编程思路。
将一行的N个大写字母按从前到后的顺序(也就是输入的顺序)看成一个字符串S。
贪心策略为:比较字符串S和将S逆序后的字符串S’,如果S较小,就从S的头部取出一个字符(即s[0]),加到T的尾部;如果S’较小,就从S的尾部取出一个字符(即s[n-1]),加到T的尾部。
(2)源程序。
#include <stdio.h>
int main()
{
int n;
scanf("%d",&n);
char s[2001];
int i;
for (i=0;i<n;i++)
{
getchar();
scanf("%c",&s[i]);
}
int left=0,right=n-1;
int cnt=0;
while (left<=right)
{
int topF=1;
for (i=0;left+i<=right;i++)
if (s[left+i]<s[right-i])
{
topF=1; break;
}
else if (s[left+i]>s[right-i])
{
topF=0; break;
}
if (topF)
{
printf("%c",s[left]); left++;
}
else
{
printf("%c",s[right]); right--;
}
cnt++;
if (cnt%80==0) printf("\n");
}
return 0;
}
注:将此源程序提交给POJ 3617 Best Cow Line(http://poj.org/problem?id=3617)可以Accepted。
【例4】整数区间。
问题描述
给定数轴上的n个区间,每个区间[a,b](a<b)都是连续的整数区间。现在要在数轴上任意取一些整数,构成一个整数集合V,要求给定的每个区间和集合V的交集至少有两个不同的元素。
求集合V最小的元素个数。
输入格式
第一行,一个数 n(1 <= n <= 10000),表示给定n个整数区间。
紧接着的n行,每行包括两个整数a和b(0 <= a < b <= 10000),表示每个区间的开始点和结束点。
输出格式
一个整数,求得的集合V最小的元素个数。
输入样例
4
3 6
2 4
0 2
4 7
输出样例
4
(1)编程思路。
先将所有区间按右端点从小到大排序。由于前后两个区间若相交,则交集一定包含前一个区间最右端的若干元素。因此,贪心策略是挑选区间最右端的两个元素加入集合V,使得加入集合的元素尽可能出现在多个相交的区间中。
定义变量begin和end分别表示当前V集合中最后的两个元素,初始化begin为第一个区间的倒数第2个元素,end为第一个区间最后的元素,所取的元素个数初始化为2(就是begin和end)。
用循环依次对第2~第n个区间进行处理
1)若第i个区间包含了begin和end这两个元素,则直接跳到下一个区间,所取的元素个数+0;
2)若第i个区间只包含了begin和end这两个元素中的一个(由于有序,所以必定是包含end),则取第i个区间的最后一个元素,所取的元素个数+1。为了方便下一区间的比较,更新begin和end的值,使它们成为当前V集合中最后的两个元素。
3)若第i个区间没有包含这两个元素,则取第i个区间的最后两个元素,更新begin和end,使它们成为当前V集合中最后的两个元素,所取的元素个数+2。
(2)源程序。
#include <stdio.h>
struct Interval
{
int left,right;
};
void sort(struct Interval a[],int n)
{
int i,j;
struct Interval t;
for (i=0;i<n-1;i++)
for (j=0;j<n-1-i;j++)
{
if (a[j].right>a[j+1].right)
{
t=a[j];
a[j]=a[j+1];
a[j+1]=t;
}
}
}
int main()
{
int n;
scanf("%d",&n);
struct Interval inter[10001];
int i;
for (i=0;i<n;i++)
scanf("%d%d",&inter[i].left,&inter[i].right);
sort(inter,n);
int begin=inter[0].right-1;
int end=inter[0].right;
int ans=2;
for (i=1;i<n;i++)
{
if (inter[i].left<=begin) // 前一区间所取的两个元素都在当前区间内
continue; // 当前区间无需取任何元素
else if (inter[i].left<=end) // 前一区间所取的元素只有一个在当前区间
{
begin=end;
end=inter[i].right;
ans++;
}
else
{
begin=inter[i].right-1;
end=inter[i].right;
ans+=2;
}
}
printf("%d\n",ans);
return 0;
}
注:将此源程序提交给POJ 1716 Integer Intervals(http://poj.org/problem?id=1716)可以Accepted。
【例5】标记点。
问题描述
在一条直线上有N个点,每个点的位置分别是Xi,现从这N个点中选择若干个点给它们加上标记。使得,对每个点而言,在其距离为R的范围内都有带有标记的点。问至少要标记几个点。
输入
输入包含多个测试用例。每个测试用例包括两行,第1行是整数R和N(其中0≤ R≤ 1000,1≤ N≤ 1000),第2行包含n个整数,表示每个点的位置x1,…,xn。由R=n=−1结束测试用例。
输出
对于每个测试用例,输出一个整数,表示最少需标记的点的数目。
输入样例
0 3
10 20 20
10 7
70 30 1 7 15 20 50
-1 -1
输出样例
2
4
(1)编程思路。
将n个点按位置值从小到大排序。对于最左边(位置值最小)的点begin,计算从点begin开始加上距离R可以到达的最远的但又小于距离R的点是哪一个,将这个点进行标记,然后以这个点为基准,重复上述的过程,最终计算出需标记的点的个数。
(2)源程序。
#include <stdio.h>
void sort(int a[],int n)
{
int i,j,t;
for (i=0;i<n-1;i++)
for (j=0;j<n-1-i;j++)
{
if (a[j]>a[j+1])
{
t=a[j];
a[j]=a[j+1];
a[j+1]=t;
}
}
}
int main()
{
while (1)
{
int r,n;
scanf("%d%d",&r,&n);
if (r==-1 && n==-1) break;
int i,x[1001];
for (i=0;i<n;i++)
scanf("%d",&x[i]);
sort(x,n);
int ans=0;
i=0;
while (i<n)
{
int begin=x[i++];
while (i<n && x[i]<=begin+r) i++;
ans++;
begin=x[i-1];
while (i<n && x[i]<=begin+r) i++;
}
printf("%d\n",ans);
}
return 0;
}
注:将此源程序提交给POJ 3069 Saruman's Army(http://poj.org/problem?id=3069)可以Accepted。
【例6】清洁工作。
问题描述
张经理分配 N(1 <= N <= 25,000)个工人中的一些工人在仓库附近做清洁。他把一天分成T段(1 <= T <= 1,000,000),第一段是1,最后一段是T。在每段中他要让至少一个工人做清洁。而每个工人只在一些时间段有空,可以做清洁。工人如果选择了某一段时间,则必须完成整段时间的工作。
请帮助张经理安排一些工人,使每段时间至少有一个工人被安排来做清洁。并且工人数应尽可能小。如果不可能办到,输出-1。
输入
输入包含多组测试数据,对于每组测试数据:
第一行:N和T
第二行至N+1行: 每一行包括工人能工作的开始和结束时间。闭区间。
输出
每组数据一行,输出完成清洁所需最少的工人数,如果不可能办到,输出-1。
输入样例
3 10
1 7
3 6
6 10
输出样例
2
(1)编程思路。
将各时间段按开始时间从小到大排序,若开始时间相同,则按结束时间从大到小。
定义整个工作的结束时间为finish,初始时finish=0(工作尚未开始)。然后对排好序的时间段进行遍历,对每个时间段,若其开始时间大于finish+1,则上一工作时间段与下一工作时间段不能连续,工作安排按要求办不到,输出-1;若其开始时间没有超过finish+1,则更新finish为可选工作时间段中最大的结束时间点。
全部时间段处理完后,若finish>=T,则问题有解,输出答案。
(2)源程序。
#include <stdio.h>
#include <algorithm>
using namespace std;
struct Interval
{
int begin,end;
};
int cmp(struct Interval a,struct Interval b)
{
if (a.begin!=b.begin)
return a.begin<b.begin;
return a.end>b.end;
}
int main()
{
int n,t;
scanf("%d%d",&n,&t);
struct Interval inter[25001];
int i;
for (i=0;i<n;i++)
scanf("%d%d",&inter[i].begin,&inter[i].end);
sort(inter,inter+n,cmp);
int finish=0,ans=0;
i=0;
while (finish<t && i<n)
{
ans++;
int tmp=finish;
if (inter[i].begin>finish+1) // 中间有缺失,无法全覆盖
break;
while (inter[i].begin<=finish+1 && i<n) // 在可选区间中选最大的结束点
{
tmp=inter[i].end>tmp?inter[i].end:tmp;
i++;
}
finish=tmp; // 更新覆盖区间结束点
}
if (finish>=t)
printf("%d\n",ans);
else
printf("-1\n");
return 0;
}
注:将此源程序提交给POJ 2376 Cleaning Shifts(http://poj.org/problem?id=2376)可以Accepted。
【例7】田忌赛马
问题描述
你一定听过田忌赛马的故事吧?
如果3匹马变成1000匹,齐王仍然让他的马按从优到劣的顺序出赛,田忌可以按任意顺序选择他的赛马出赛。赢一局,田忌可以得到200两银子,输一局,田忌就要输掉200两银子,平局的话不输不赢。 问田忌最多能赢多少银子?
输入
输入包含多组测试数据,每组测试数据的第一行是一个整数n(1<=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
(1)编程思路。
贪心策略是:如果田忌当前速度最快的马可以胜齐王最快的马,那么让这两匹马比一场;如果田忌当前速度最慢的马能胜齐王最慢的马,那么让这两匹马比一场;如果上面两个条件都不满足,就让田忌当前速度最慢的马和齐王最快的马比一场。
(2)源程序。
#include <stdio.h>
void sort(int a[],int n)
{
int i,j,tmp;
for (i=0;i<n-1;i++)
for (j=0;j<n-1-i;j++)
if (a[j]>a[j+1])
{
tmp=a[j]; a[j]=a[j+1]; a[j+1]=tmp;
}
}
int main()
{
int n;
while (scanf("%d",&n) && n!=0)
{
int tj[1001],king[1001];
int i;
for (i=0;i<n;i++)
scanf("%d",&tj[i]);
for (i=0;i<n;i++)
scanf("%d",&king[i]);
sort(tj,n);
sort(king,n);
int money=0;
int tBegin=0,tEnd=n-1;
int kBegin=0,kEnd=n-1;
while (tBegin<=tEnd)
{
if (tj[tBegin]>king[kBegin])
{
tBegin++; kBegin++; money+=200;
}
else if (tj[tEnd]>king[kEnd])
{
tEnd--; kEnd--; money+=200;
}
else
{
if (tj[tBegin]<king[kEnd])
money-=200;
tBegin++; kEnd--;
}
}
printf("%d\n",money);
}
return 0;
}
注:将此源程序提交给POJ 2287 Tian Ji--The Horse Racing(http://poj.org/problem?id=2287)可以Accepted。
【例8】发工资。
问题描述
老王要给他雇佣的保姆发工资,每天不得低于C元,多于C的钱保姆不会找零的,权当老王给了小费了。老王有n种面值的钱币,第i种钱币的面值为vi,数量有bi张。问老王的这些钱最多可给他的保姆发多少天的工资。
输入
第1行为两个空格分隔的整数:N和C
第2…N+1行:每一行对应一种钱币的面额,并包含两个整数:面额的值V(1<=V<=100000000)和老王拥有的这种面额的钱币数量B(1<=B<=1000000)。
输出
一个整数,表示老王至少可以向保姆支付C工资的天数。
输入样例
3 6
10 1
1 100
5 120
输出样例
111
(1)编程思路。
为了尽可能的发更多的天数,每次发的工资都应尽可能贴近C。为此先将把面值大于或等于C的钱币直接发完。再将剩余钱币按面值从大到小排列,之后进行组合发放。组合发放时,先从大到小进行发放,发放过程中,工资不能超过C;然后,如果工资不超过C,就用面值小的进行补偿,即按面值从小到大进行补偿,使得工资可以超过C,因为是从小到大补偿,这样可以将小面值用到极致,也就是能用的都用了。具体采用的贪心方法为:
1)按面值从大往小取,把面值凑到最大但不大于等于c,同时减少使用的钱币数量;
2)在按面值从小往大取,把面值凑到刚好大于等于c,同时减少使用的钱币数量,再重复这两步。
(2)源程序。
#include <stdio.h>
struct Coin
{
int v,b;
};
int main()
{
int n,c;
scanf("%d%d",&n,&c);
struct Coin a[21];
int i,j,v,b,cnt=0;
int ans=0;
for (i=0;i<n;i++)
{
scanf("%d%d",&v,&b);
if (v>=c) { ans+=b; continue; } // 面值大于或等于C的直接统统发完
a[cnt].v=v; a[cnt].b=b; cnt++;
}
for (i=0;i<cnt-1;i++)
for (j=0;j<cnt-1-i;j++)
if (a[j].v<a[j+1].v)
{
struct Coin tmp;
tmp=a[j]; a[j]=a[j+1]; a[j+1]=tmp;
}
while (1)
{
int remain=c;
for (i=0;i<cnt;i++)
{
while (remain>=a[i].v && a[i].b)
{
remain-=a[i].v;
a[i].b--;
}
}
if (remain>0)
for (i=cnt-1; i>=0;i--)
{
if (a[i].v>=remain && a[i].b)
{
a[i].b--;
remain-=a[i].v;
break ;
}
}
if (remain>0) break ;
ans++;
}
printf("%d\n",ans);
return 0;
}
注:将此源程序提交给POJ 3040 Allowance(http://poj.org/problem?id=3040)可以Accepted。
【例9】分遗产。
问题描述
将一份遗产分成n份,分别为1/X1、1/X2、1/X3、…、1/Xn。 使得X1 >= X2 >= X3 >= ... >= Xn,都为正整数。最后剩下的分给教堂,让其分得的财产最少,但不能没有。
输入
输入为一个整数N(1 <= N <= 18)。
输出
输出n行,每行为一份遗产的具体分法,即第i行输出Xi的值。
输入样例
2
输出样例
2
3
(1)编程思路。
贪心策略是:要使留给教堂的遗产最少,前面每个人应尽可能在其可能分的遗产中分最多的一块。
若1个人分,显然拿走1/2,剩下1/2给教堂。
若2个人分,第1个人拿走1/2,剩下1/2中第2个人尽可能拿走最多,这样可拿走1/3,剩下1/6(1-1/2-1/3)给教堂。
若3个人分,第1个人拿走1/2,剩下1/2中第2个人拿走1/3,剩下1/6中第3个人最多可拿走1/7,剩下1/42(1-1/2-1/3-1/7)给教堂。
若4个人分,第1个人拿走1/2,第2个人拿走1/3,第3个人拿走1/7,剩下1/42中第4个人最多可拿走1/43,剩下1/1806(1-1/2-1/3-1/7-1/43)给教堂。
由此可知,n份遗产的分法。
设p[i]为每个人分到的遗产部分的分母,设last[i]为当N=i时剩下的部分的分母,则对于i=2~N有:
p[i]=last[i-1]+1
last[i]=p[i]*last[i-1]
初始时,p[1]=2 , last[1]=2。
由于乘积增长过快,超出了整数的表数范围,因此需要采用高精度运算。
(2)源程序。
#include <stdio.h>
#include <string.h>
#define MAX_LEN 60000
void bigNumAddOne(char a[],char c[])
{
int i,j,n;
int num1[MAX_LEN]={0};
n = strlen(a);
j = 0;
for (i = n - 1;i >= 0 ; i --)
num1[j++] = a[i] - '0';
num1[0]++;
for (i = 0;i < n ; i ++ )
{
if (num1[i] >= 10 ) // 处理进位
{
num1[i] -= 10;
num1[i+1] ++;
}
else
break;
}
j=0;
if (num1[n]!=0) c[j++]=num1[n]+'0';
for(i=n-1; i>=0; i--)
c[j++]=num1[i]+'0';
c[j]='\0';
}
void charTobignum(char *ch,int *bignum)
{
int len,i,j,p,num;
memset(bignum,0,sizeof(int)*MAX_LEN);
len=strlen(ch);
bignum[0]=len%4==0?len/4:len/4+1;
i=1;
while (i<=len/4)
{
num=0;
p=len-4*i;
for(j=1;j<=4;j++)
num=num*10+(ch[p++]-'0');
bignum[i]=num;
i++;
}
if (len%4!=0)
{
num=0;
for (i=0;i<=len%4-1;i++)
num=num*10+(ch[i]-'0');
bignum[len/4+1]=num;
}
}
void bigNumMul(char a[],char b[],char c[])
{
int i,j;
int num1[MAX_LEN]={0},num2[MAX_LEN]={0},result[2*MAX_LEN]={0};
charTobignum(a,num1);
charTobignum(b,num2);
int carry=0;
memset(result, 0, sizeof(int)*2*MAX_LEN);
for (j=1; j<=num2[0]; j++){
for(i=1; i<=num1[0]; i++){
result[i+j-1]+=carry+num1[i]*num2[j];
carry=result[i+j-1]/10000;
result[i+j-1]%=10000;
}
i=j+num1[0];
while(carry){
result[i++]=carry%10000;
carry/=10000;
}
}
result[0]=num1[0]+num2[0];
while( !result[*result] ) --result[0];
int n=0;
int x=result[result[0]];
i=result[0]-1;
if (x<10) c[n++]=x+'0';
else if (x<100) { c[n++]=x/10+'0'; c[n++]=x%10+'0';}
else if (x<1000) { c[n++]=x/100+'0'; c[n++]=x%100/10+'0'; c[n++]=x%10+'0';}
else i++;
for (;i>=1;i--)
{
x=result[i];
c[n++]=x/1000+'0'; c[n++]=x%1000/100+'0';
c[n++]=x%100/10+'0'; c[n++]=x%10+'0';
}
c[n]='\0';
}
char p[19][MAX_LEN],last[19][MAX_LEN];
int main()
{
int n;
scanf("%d",&n);
last[1][0]='2'; last[1][1]='\0';
p[1][0]='2'; p[1][1]='\0';
int i;
for (i=2;i<=n;i++)
{
bigNumAddOne(last[i-1],p[i]);
bigNumMul(p[i],last[i-1],last[i]);
}
for (i=1;i<=n;i++)
printf("%s\n",p[i]);
return 0;
}
注:将此源程序提交给POJ 1405 Heritage(http://poj.org/problem?id=1405)可以Accepted。
【例10】各位数字的乘积。
问题描述
一个整数各个位的数字相乘,可以得到一个新的数,继续各个位相乘,最后得到一个数,比如:679 -> 378 -> 168 -> 48 -> 32 -> 6
给定一个数,求一个最小的数,使得其各位乘积是这个数。
输入
对于每个测试用例,都有一行输入,为一个最多1000位的十进制整数。最后一个测试用例后面有一行,为-1,代表输入结束。
输出
对于每个测试用例,输出一行,其中包含一个满足上述条件的整数,或者输出一条语句,说明下面所示的格式中没有这样的数字。
输入样例
0
1
4
7
18
49
51
768
-1
输出样例
10
11
14
17
29
77
There is no such number.
2688
(1)编程思路。
要使结果为最小数,首先位数应尽可能小,这样因子用9优于3*3,8优于2*4或2*2*2;其次较大的因子一定要放在较小的因子的后面。因此贪心策略为:因子从最大9枚举到2,把最大的因子放在最低位。
具体做法是:将输入的大数除以9,无法整除再除以 8、7、6、…、2,如果可以整除就将除数记录,将商作为被除数继续除以9、8、…、2。最后如果商为1,则将被除过的数从小到大输出即可。若商为2位以上的数,则输出无此数的提示。
当输入的整数为1位数(<=9)时,特殊处理,简单将其十位加上一个1即可。
(2)源程序。
#include <stdio.h>
#include <string.h>
char s[1005];
char quotient[1005]; // 存储高精度除法的商
int result[1005];
int bigDiv(int x)
{
int i,cnt=0,num=0;
for (i=0;s[i]!='\0';i++)
{
num=num*10+s[i]-'0';
quotient[cnt++]=num/x+'0';
num%=x;
}
quotient[cnt]='\0';
if (num==0) // 余数为0,可以整除x
{
i=0;
while(quotient[i]=='0')
i++;
strcpy(s,quotient+i); // 复制字符串时去掉前导0
return 1;
}
return 0;
}
int main()
{
while(1)
{
scanf("%s",s);
if(s[0]=='-') break;
if(strlen(s)<2)
{
printf("1%s\n",s);
continue;
}
int cnt=0;
int i=9;
while(i>1)
{
if(bigDiv(i))
{
result[cnt++]=i;
}
else
i--;
}
if(strlen(s)>1)
printf("There is no such number.\n");
else
{
for(i=cnt-1;i>=0;i--)
printf("%d",result[i]);
printf("\n");
}
}
return 0;
}
注:将此源程序提交给POJ 2325 Persistent Numbers(http://poj.org/problem?id=2325)可以Accepted。
在理解了贪心法及其应用方法的基础上,可以刷一下如下的POJ题库中的10道题目。这几道题目均可以采用贪心法解决。
POJ 1065 Wooden Sticks(http://poj.org/problem?id=1065)
#include <stdio.h> #include <algorithm> using namespace std; struct stick { int l; int w; int flag; }; int cmp(struct stick a, struct stick b) { if(a.l!=b.l) return a.l<b.l; else return a.w<b.w; } int main() { int nCase; scanf("%d",&nCase); while (nCase--) { int n,i,j; scanf("%d",&n); struct stick a[5001]; for (i=0;i<n;i++) { scanf("%d%d",&a[i].l,&a[i].w); a[i].flag=0; } sort(a,a+n,cmp); int time=0,tmp; for(i=0;i<n;i++) { if(a[i].flag==0) { tmp=a[i].w; a[i].flag=1; time++; for (j=i+1;j<n;j++) { if (a[j].w>=tmp && a[j].flag==0) { a[j].flag=1; tmp=a[j].w; } } } } printf("%d\n",time); } return 0; }
POJ 1230 Pass-Muraille(http://poj.org/problem?id=1230)
// 贪心策略:从左往右扫描每一列,如果这一列不符合要求,则依次拆除 // 该列中向右延伸得最长的那些墙,直到符合要求。 #include <stdio.h> #include <string.h> int main() { int t; scanf("%d",&t); while(t--) { int map[101][101]; memset(map,0,sizeof(map)); int max_x=0; // 所有墙最大列坐标 int max_y=0; // 所有墙最大行坐标 int sum=0; // 最少拆除墙面数 int n,k; scanf("%d %d",&n,&k); int i,j; for (i=1;i<=n;i++) { int x1,y1,x2,y2,tmp; scanf("%d%d%d%d",&x1,&y1,&x2,&y2); if(x1>max_x) max_x=x1; if(x2>max_x) max_x=x2; if(y1>max_y) max_y=y1; if (x1>x2) { tmp=x1; x1=x2; x2=tmp; } for (j=x1;j<=x2;j++) map[y1][j]=i; // 同一面墙标注值相同 } for (i=0;i<=max_x;i++) // 从左到右扫描每一列 { int cnt=0; for (j=0;j<=max_y;j++) // 统计第i列中墙的格子数 if(map[j][i]>0) cnt++; int del=cnt-k; if (del>0) { sum+=del; while (del--) { int max_s=0,max_bh;//最多的格子数,行数 for (j=0;j<=max_y;j++) // 搜索第i列每个有墙的格子,统计其右方属于同堵墙的格子数 { if (map[j][i]>0) { cnt=0; for (int q=i+1;q<=max_x;q++) { if (map[j][q]==map[j][i]) cnt++; } if (max_s<cnt) { max_s=cnt; max_bh=j; } } } for (j=i;j<=i+max_s;j++) // 拆除含格子数最多的墙 map[max_bh][j]=0; // 拆除多余的墙 } } } printf("%d\n",sum); } return 0; }
POJ 1456 Supermarket(http://poj.org/problem?id=1456)
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; struct Product { int p,d; }; int cmp(struct Product a,struct Product b) { if (a.p==b.p) return a.d<b.d; return a.p>b.p; } int main() { int n; while (scanf("%d",&n)!=EOF) { struct Product prod[10010]; int hash[10010]; memset(hash,0,sizeof(hash)); int i,j; for (i=0;i<n;i++) scanf("%d%d",&prod[i].p,&prod[i].d); if (n==0) { printf("0\n"); continue; } sort(prod,prod+n,cmp); int sum=0; for (i=0;i<n;i++) { if (hash[prod[i].d]==0) { sum+=prod[i].p; hash[prod[i].d]=1; } else { for (j=prod[i].d-1;j>=1;j--) { if (hash[j]==0) { sum+=prod[i].p; hash[j]=1; break; } } } } printf("%d\n",sum); } return 0; }
POJ 1548 Robots(http://poj.org/problem?id=1548)
#include <stdio.h> #include <algorithm> using namespace std; struct Garbage { int x; int y; int flag; }; int cmp(struct Garbage a, struct Garbage b) { if(a.x!=b.x) return a.x<b.x; else return a.y<b.y; } int main() { while (1) { int x,y,n,i,j; scanf("%d%d",&x,&y); if (x==-1 && y==-1) break; n=0; struct Garbage a[625]; a[n].x=x; a[n].y=y; a[n].flag=0; n++; while (1) { scanf("%d%d",&x,&y); if (x==0 && y==0) break; a[n].x=x; a[n].y=y; a[n].flag=0; n++; } sort(a,a+n,cmp); int time=0,tmp; for(i=0;i<n;i++) { if(a[i].flag==0) { tmp=a[i].y; a[i].flag=1; time++; for (j=i+1;j<n;j++) { if (a[j].y>=tmp && a[j].flag==0) { a[j].flag=1; tmp=a[j].y; } } } } printf("%d\n",time); } return 0; }
POJ 2620 Minimal coverage(http://poj.org/problem?id=2620)
#include <stdio.h> #include <algorithm> using namespace std; struct Point { int x, y; }; int cmp(struct Point a, struct Point b) { return a.y > b.y; } int main() { int m; scanf("%d", &m); struct Point a[100001],ans[100001]; int n=0; while (1) { scanf("%d%d", &a[n].x, &a[n].y); if (a[n].x == 0 && a[n].y == 0) break; n++; } int i,j; sort(a,a+n,cmp); int start = 0; int cnt=0; while (start <m) { for (i = 0; i < n; i++) if (a[i].x <= start && a[i].y > start) { start = a[i].y; ans[cnt++]=a[i]; break; } if (i == n) break; } if (start < m) printf("No solution\n"); else printf("%d\n", cnt); for (i = 0; i < cnt; i++) printf("%d %d\n", ans[i].x, ans[i].y); return 0; }
POJ 2709 Painter(http://poj.org/problem?id=2709)
#include <stdio.h> void sort(int a[],int n) { int i,j,t; for (i=0;i<n-1;i++) for (j=0;j<n-1-i;j++) if (a[j]>a[j+1]){ t=a[j]; a[j]=a[j+1]; a[j+1]=t; } } int main() { int color[13]; while(1) { int n,i; scanf("%d",&n); if (n==0) break; for (i=0;i<n;i++) scanf("%d",&color[i]); int grey; scanf("%d",&grey); sort(color,n); while(grey--) { color[0]++; color[1]++; color[2]++; sort(color,n); } int ans=(color[n-1]+49)/50; printf("%d\n",ans); } return 0; }
POJ 2782 Bin Packing(http://poj.org/problem?id=2782)
#include <stdio.h> #include <algorithm> using namespace std; int main() { int n; scanf("%d",&n); int l; scanf("%d",&l); int p[100001]; int i,j; for (i=0;i<n;i++) { scanf("%d",&p[i]); } sort(p,p+n); int ans=0; i=0; j=n-1; while (i<j) { if (p[i]+p[j]>l) j--; else { i++; j--; } ans++; } if (i==j) ans++; printf("%d\n",ans); return 0; }
POJ 3253 Fence Repair(http://poj.org/problem?id=3253)
// 哈夫曼树的构造思想。 #include <stdio.h> int main() { int n; scanf("%d",&n); int a[20001]; int i,j,t; for (i=0;i<n;i++) scanf("%d",&a[i]); for (i=0;i<n-1;i++) for (j=0;j<n-1-i;j++) { if (a[j]>a[j+1]) { t=a[j]; a[j]=a[j+1]; a[j+1]=t; } } long long ans=0; for (i=0;i<n-1;i++) { t=a[i]+a[i+1]; ans+=t; for (j=i+1;j<n-1;j++) if (a[j+1]<t) a[j]=a[j+1]; else break; a[j]=t; } printf("%lld\n",ans); return 0; }
POJ 3270 Cow Sorting(http://poj.org/problem?id=3270)
#include <stdio.h> struct node { int no,val; }; struct node b[100000]; int a[100000]; int main() { int n; scanf("%d",&n); int i,j; for (i=0;i<n;i++) { scanf("%d",&a[i]); b[i].val=a[i]; b[i].no=i; } for (i=0;i<n-1;i++) for (j=0;j<n-1-i;j++) if (b[j].val>b[j+1].val) { struct node tmp; tmp=b[j]; b[j]=b[j+1]; b[j+1]=tmp; } int ans=0,t; for (i=0;i<n;i++) if (a[i]!=b[i].val) { int id=b[i].no; int t1=2*(b[0].val+a[id]); // 方案1,最小值参加循环节交换 int t2=0; // 方案2,直接循环节交换 while (a[id]!=b[id].val) { t1+=b[0].val+a[b[id].no]; t2+=a[id]+a[b[id].no]; t=a[id]; a[id]=a[b[id].no]; a[b[id].no]=t; id=b[id].no; } ans+=(t1<t2?t1:t2); } printf("%d\n",ans); return 0; }
POJ 3544 Journey with Pigs(http://poj.org/problem?id=3544)
// 贪心策略为:先求出每斤猪肉在每个村庄出售后能赚到的钱数。然后将体重最重的猪卖给每斤猪肉赚钱数最多的村庄。 #include <stdio.h> struct Village { long long d,p; int num; }; struct Pig { int w; int num; }; int main() { int n; long long t; scanf("%d%lld",&n,&t); struct Village v[1001]; struct Pig p[1001]; int i,j; for (i=0;i<n;i++) { scanf("%d",&p[i].w); p[i].num=i; } for (i=0;i<n;i++) { scanf("%lld",&v[i].d); v[i].num=i; } for (i=0;i<n;i++) { scanf("%lld",&v[i].p); v[i].p=v[i].p-t*v[i].d; // 每斤猪肉的实际价格 } struct Village tmp1; for (i=0;i<n-1;i++) for (j=0;j<n-1-i;j++) if (v[j].p<v[j+1].p) { tmp1=v[j]; v[j]=v[j+1]; v[j+1]=tmp1; } struct Pig tmp2; for (i=0;i<n-1;i++) for (j=0;j<n-1-i;j++) if (p[j].w<p[j+1].w) { tmp2=p[j]; p[j]=p[j+1]; p[j+1]=tmp2; } int ans[1001]; for (i=0;i<n;i++) { ans[v[i].num]=p[i].num; // 将村庄和猪一一对应 } for (i=0;i<n;i++) printf("%d ",ans[i]+1); printf("\n"); return 0; }