一、区间选点问题
问题描述:给定若干个区间,要求用最少的点,使得每个区间内都包含至少一个点
根据贪心思想,我们只需要在每个区间的右端点安置点即可,这样如果两个区间相交的话,它们可以共用一个点。
首先根据线段右端点为第一关键字升序、左端点为第二关键字降序排序,如上图所示,由上到下线段编号逐渐增大,维护一个边界End,每次加入一条新的线段,检查这条线段的左端点是否比End大,如果比End大,则需要增加一个点,并更新End值(没有相交,不能共用一个点),否则跳过(两区间相交,可以共用一个点)
1、POJ 1328 Radar Installation
http://poj.org/problem?id=1328
覆盖每个点的圆的圆心的区间如上图中绿色线段所示,对于每个象征岛屿的点,我们只需要找出覆盖它的圆心区间即可,然后便能套用区间选点的贪心思想来做,但是这个题很容易错,需要小心
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cmath>
#include <algorithm>
#define MAXN 1010
using namespace std;
struct Line
{
double l,r;
}line[MAXN];
bool cmp(Line a,Line b)
{
return a.l<b.l;
}
int main()
{
int n,d,Case=0;
while(++Case)
{
int ans=1;
scanf("%d%d",&n,&d);
if(!n&&!d) break;
bool flag=true;
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
if(d>=y&&flag==true)
{
line[i].l=x-sqrt((double)d*d-(double)y*y);
line[i].r=x+sqrt((double)d*d-(double)y*y);
}
else flag=false;
}
if(!flag) //无论如何雷达也覆盖不住所有点
{
printf("Case %d: -1\n",Case);
continue;
}
sort(line+1,line+n+1,cmp); //按线段右端点升序排序,左端点为第二关键字降序排序
double End=line[1].r;
for(int i=2;i<=n;i++)
{
if(End<line[i].l)
{
End=line[i].r;
ans++;
}
else if(line[i].r<End) End=line[i].r;
}
printf("Case %d: %d\n",Case,ans);
}
return 0;
}
二、其他类型的贪心
1、POJ 2287 Tian Ji -- The Horse Racing
http://poj.org/problem?id=2287
题目说白了就是田忌赛马的问题,输入田忌和国王每匹马的性能,田忌每次比赛胜一场得200元,输一次扣200元,求田忌最后最多能拿到多少钱
我们可以分4类情况讨论
1、田忌最快马比国王最快马快,不用说,当然就是让田忌最快马和国王最快马比一次,田忌得200
2、田忌最快马比国王最快马慢,那么田忌所有的马都比不上国王的最快马,注定这局要输,干脆让田忌的最慢马去和国王最快马比得了,让他的最慢马消耗掉国王的最快马,田忌扣200
如果田忌最快马和国王最快马速度一样,则比较他们的最慢马
3、如果田忌最慢马比国王最快马快,那么注定这局田忌赢,用太快的马和国王的最慢马比就浪费了,所以让田忌最慢马和国王最慢马比,田忌得200
4、如果田忌最慢马比国王最慢马慢,那么田忌的这匹最慢马无论如何都不可能赢,但田忌其他的马和国王最慢马比有可能赢,田忌的这匹马归根结底是要输的,就让它去和国王最快马比,消耗掉国王的最快马
5、否则,这局平局。
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#define MAXN 1010
using namespace std;
int TianJiHorse[MAXN],KingHorse[MAXN]; //田忌的马速度、国王的马速度
int main()
{
while(1)
{
memset(TianJiHorse,0,sizeof(TianJiHorse));
memset(KingHorse,0,sizeof(KingHorse));
int n,ans=0; //ans=最终田忌得到的最多钱数
scanf("%d",&n);
if(!n) break;
for(int i=1;i<=n;i++)
scanf("%d",&TianJiHorse[i]);
for(int i=1;i<=n;i++)
scanf("%d",&KingHorse[i]);
sort(TianJiHorse+1,TianJiHorse+n+1);
sort(KingHorse+1,KingHorse+n+1);
int minTian=1,minKing=1,maxTian=n,maxKing=n,cnt=0; //cnt=赛马次数
while((++cnt)<=n)
{
if(TianJiHorse[maxTian]>KingHorse[maxKing]) //田忌最快马快于国王最快马,就让两匹马比赛
{
ans+=200;
maxTian--;
maxKing--;
}
else if(TianJiHorse[maxTian]<KingHorse[maxKing]) //如果田忌最快马比国王最快马慢,让田忌最慢马和国王最快马比赛,因为无论任何一匹马都比不过国王最快马
{
ans-=200;
minTian++;
maxKing--;
}
else //若两匹最快马相等,则比较最慢马
{
if(TianJiHorse[minTian]>KingHorse[minKing]) //如果田忌最慢马比国王最慢马快,则让两匹马比赛
{
ans+=200;
minTian++;
minKing++;
}
else //若田忌最慢马比国王最慢马慢,则让它和国王最快马比赛,因为这匹马必输无疑
{
if(TianJiHorse[minTian]<KingHorse[maxKing]) ans-=200;
minTian++; //如果没扣钱,就是平局了
maxKing--;
}
}
}
printf("%d\n",ans);
}
return 0;
}
2、POJ 3040 Allowance
http://poj.org/problem?id=3040
题意:农民每周要给一头奶牛发工资,每周工资不低于C,给出农民手头上的每种硬币的面值和个数,求农民最多能给奶牛发多少周的工资
思路:将硬币按面值升序排序,首先将面值大于C的硬币全部发光(只用这些硬币就够发好多周的工资了,把它们和小面值的硬币一起发的话浪费了),然后从大到小遍历剩下的硬币面值,凑出一个面额x(每种硬币可用多个),使得x<C且尽可能大,然后从小到大遍历硬币面值,在x的基础上加硬币(每种硬币同样可用多个),使得x加硬币后大于C且尽可能小,计算以这样的一种方案最多能发多少周工资,累加答案。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#define MAXN 22
#define INF 0x3f3f3f3f
using namespace std;
struct Coin
{
int v,num; //硬币的面值、个数
}coins[MAXN];
int need[MAXN]; //need[i]=第i种硬币用的个数
int numOfCoins=0,valueOfCoins=0; //总硬币数、硬币总价值
bool cmp(Coin a,Coin b)
{
return a.v<b.v;
}
int min(int a,int b)
{
if(a<b) return a;
return b;
}
int main()
{
int n,c,ans=0;
scanf("%d%d",&n,&c);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&coins[i].v,&coins[i].num);
}
sort(coins+1,coins+n+1,cmp);
for(int i=1;i<=n;i++)
{
if(coins[i].v>=c) //一种硬币价值比c大,则节约不了这个硬币,在一个星期把它发掉
{
ans+=coins[i].num;
coins[i].num=0;
}
}
while(1)
{
int sum=c; //等待凑足sum元
memset(need,0,sizeof(need));
for(int i=n;i>=1;i--) //从大到小凑硬币
{
if(sum>0&&coins[i].num>0) //这一周工资还没凑足,且当前种类硬币有货
{
int NumOfNeed=min(sum/coins[i].v,coins[i].num); //NumOfNeed=需要从这一种硬币拿掉的硬币个数
if(NumOfNeed>0) //需要拿,就扣除这部分硬币
{
sum-=NumOfNeed*coins[i].v;
need[i]=NumOfNeed;
}
}
}
for(int i=1;i<=n;i++) //从小到大凑硬币,把大硬币没有凑齐的部分补上
{
if(sum>0&&coins[i].num>0)
{
int NumOfNeed=min((sum+coins[i].v-1)/coins[i].v,coins[i].num-need[i]);
if(NumOfNeed>0) //这种硬币需要拿,就扣除这部分硬币
{
sum-=NumOfNeed*coins[i].v;
need[i]+=NumOfNeed;
}
}
}
if(sum>0) //凑不齐,就不再尝试了
break;
int AddNum=INF; //凑起来的发工资次数,这个次数取决于每种要拿的硬币,可以满足发工资的请求次数的最小值
for(int i=1;i<=n;i++)
{
if(!need[i]) continue;
AddNum=min(AddNum,coins[i].num/need[i]);
}
ans+=AddNum;
//开始扣除每种硬币的个数
for(int i=1;i<=n;i++)
{
if(!need[i]) continue;
coins[i].num-=AddNum*need[i];
}
}
cout<<ans<<endl;
return 0;
}
三、单调栈、单调队列与滑动窗口
1、POJ 3250 Bad Hair Day(单调栈)
http://poj.org/problem?id=3250
题目大意:给定一个牛的高度序列,每头牛只能看见右边比它矮的牛,求所有牛能看到其他牛的个数之和。
比如上图,黄色牛可以被左边的这些蓝色牛看到。
由于题意特殊,每头牛可以看到的其他牛的高度不一定非要单调递减,所以不能用DP的LIS来求,但是可以维护一个单调栈,保证栈中上面的元素比下面的元素要小,每次放入一头牛,就更新这个单调栈,将栈顶所有比这头牛高度小的都弹出去,然后将栈的元素加入答案。
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#define MAXN 81000
using namespace std;
int priorityStack[MAXN],top=1;
int main()
{
int n,h;
long long int ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&h);
while(top>0)
{
if(priorityStack[top]<=h)
top--;
else break;
}
ans+=top;
priorityStack[++top]=h;
}
printf("%lld\n",ans);
return 0;
}
2、POJ 2823 Sliding Window(滑动窗口&单调队列模板题)
http://poj.org/problem?id=2823
题意:给定一个长度为n的序列,滑动窗口长度为k,求在不同位置时滑动窗口中元素的最大值和最小值
用单调队列来维护滑动窗口中的最大值,保证单调队列单调递减,滑动窗口每向右移动一次,就让队尾比新的元素小的元素出队,并将新的元素入队;将队首不属于新的滑动窗口的元素出队,经过上述的维护,单调队列的队首就是新的滑动窗口最大值。单调队列维护滑动窗口最小值同上。
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#define MAXN 1000100
using namespace std;
int MonoQueue[MAXN],pointer=1,n,k;
int a[MAXN];
int index[MAXN];
void getMax()
{
int head=0,tail=1;
for(int i=1;i<k;i++)
{
while(head<tail&&MonoQueue[tail-1]<=a[i]) //Monoq is not empty
tail--;
MonoQueue[tail++]=a[i]; //push the new element into Mono queue
index[tail-1]=i;
}
for(int i=k;i<=n;i++)
{
while(head<tail&&MonoQueue[tail-1]<=a[i])
tail--;
MonoQueue[tail++]=a[i];
index[tail-1]=i;
while(index[head]<=i-k) head++; //队首不属于新的窗口的元素出队
printf("%d ",MonoQueue[head]);
}
}
void getMin()
{
int head=0,tail=1;
for(int i=1;i<k;i++)
{
while(head<tail&&MonoQueue[tail-1]>=a[i]) //Monoq is not empty
tail--;
MonoQueue[tail++]=a[i]; //push the new element into Mono queue
index[tail-1]=i;
}
for(int i=k;i<=n;i++)
{
while(head<tail&&MonoQueue[tail-1]>=a[i])
tail--;
MonoQueue[tail++]=a[i];
index[tail-1]=i;
while(index[head]<=i-k) head++; //队首不属于新的窗口的元素出队
printf("%d ",MonoQueue[head]);
}
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
getMin();
printf("\n");
getMax();
printf("\n");
return 0;
}
四、二分
1、POJ 3258 River Hopscotch
http://poj.org/problem?id=3258
题目大意:给定n个石头距离起点0的长度、终点长度,要删去其中m个石头,求最终相邻石头之间的最小距离的最大值。
可以通过二分来猜这个相邻石头之间的最小距离,然后根据这个猜出来的最小距离,把比最小距离小的石头都删去,如果删去石头太多,减小二分上界,否则增大二分上界。
注意:终点和起点不能删除,因此终点的情况要单独考虑,如果除终点外最右边的一个石头与终点距离太近,则需要再删除掉一个石头(最右边的那个石头)。
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>
#define MAXN 51000
using namespace std;
int rock[MAXN]; //石头距起点的距离
int l,n,m;
int binarySearch() //二分查找答案
{
int upperBound=l,mid,lowerBound=0;
while(upperBound>=lowerBound)
{
int cnt=0; //删去石头的个数
int pre=1; //这一个石头之前的一个石头
mid=(upperBound+lowerBound)/2; //mid=两石头之间最小距离
for(int i=2;i<=n+1;i++) //删去距离终点小于mid的石头
{
if(rock[i]-rock[pre]<mid) //第i个石头和之前一个石头之间距离小于mid,要删掉第i个石头
cnt++;
else pre=i;
}
if(l-rock[pre]<mid) cnt++;
if(cnt>m) //删多了
upperBound=mid-1;
else //删少了
lowerBound=mid+1;
}
return upperBound;
}
int main()
{
scanf("%d%d%d",&l,&n,&m);
for(int i=2;i<=n+1;i++) scanf("%d",&rock[i]);
rock[1]=0,rock[n+2]=l;
sort(rock+1,rock+n+3);
printf("%d\n",binarySearch());
return 0;
}
洛谷P2088 果汁店的难题
[友情提示:本店不售卖混合果汁]
每组测试数据第一行包括两个整数K,N(1<=K<=10,1<=N<=100), 其中,K表示小王准备了K台干净的榨汁机,N表示排队等待的有N个客人,接下来N行,每行一个整数表示一个客人点的果汁种类Xi(1<=xi<=100).
输出格式:
输出在当前的请求序列下,小王最少需要清洗果汁机的次数
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define MAXN 1000
using namespace std;
bool hasPeople[MAXN];
int tot=0; //tot=果汁机个数
int color[MAXN]; //每种榨汁机的果汁种类
int kind[10];
int juice[MAXN]; //果汁请求
int main()
{
int k,n,ans=0;
cin>>k>>n;
for(int i=1;i<=n;i++)
{
scanf("%d",&juice[i]);
}
for(int i=1;i<=k;i++)
{
color[i]=juice[i];
juice[i]=0;
}
ans=k;
for(int i=k+1;i<=n;i++)
{
bool flag=true; //flag=true表示已经用过的机器中没有第k+1个请求的果汁种类的
for(int j=1;j<=k;j++)
if(color[j]==juice[i])
{
flag=false;
break;
}
if(flag) //找不到这种果汁种类的榨汁机,则必须洗一台榨汁机
{
int NumOfJuice=0,NumOfMechine=0;
for(int j=1;j<=k;j++) //机器编号为j
for(int l=i;l<=n;l++) //果汁订单编号为l
{
if(juice[l]==color[j]) //j和l种类相同
{
if(l>NumOfJuice) //l的编号比之前找到的大
{
NumOfJuice=l;
NumOfMechine=j;
}
break;
}
if(l==n)
{
NumOfJuice=n;
NumOfMechine=j;
}
}
ans++;
color[NumOfMechine]=juice[i];
}
juice[i]=0;
}
cout<<ans-k<<endl;
return 0;
}