贪心——区间问题
一、区间选点问题
1、区间问题
1、思路
给定N个闭区间 [ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。输出选择的点的最小数量。
将每个区间按照右端点从小到大排序,然后从前往后依次枚举每个区间。
如果下一个区间和当前区间没有交点,就新开一个,点+1
如果有交点,直接过。
当一个问题是单峰的,可以用贪心,因为贪心选的是当前的最优解,
如果是多峰,可能有问题,即局部最优解不一定是全局最优解
2、代码模板:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
struct Range{ //结构体存区间,左右边界
int l,r;
bool operator< (const Range &W)const//重载运算符<
{
return r<W.r;
}
}range[N];
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
{
int l,r;
scanf("%d %d",&l,&r);//输入左右端点
range[i]={l,r};
}
sort(range,range+n);//按照右端点从小到大排序
int res=0,ed=-2e9;//初始化左右边界
for(int i=0;i<n;i++)//枚举n个区间
{
if(range[i].l>ed)//如果下一区间的左边界大于上一个区间的右边界,即两区间不相交
{
res++; //点+1
ed=range[i].r;//右边界更新为新区间的右边界
}
}
printf("%d",res);
return 0;
}
2、最小不相交数量.908
1、思路
与第一题思路相同
按照每个区间的右边界从小到大排序
然后依次枚举,如果下一区间的左边界大于上一区间的右边界,即不相交,区间数+1
相交就更新右端点,区间数不变
2、代码模板:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
struct Range{ //结构体存区间,左右边界
int l,r;
bool operator < (const Range &W)const//重载运算符<
{
return r<W.r;
}
}range[N];
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d %d",&range[i].l,&range[i].r);
sort(range,range+n);//按照右端点从小到大排序
int res=0,ed=-2e9;//初始化右端点
for(int i=0;i<n;i++)
{
if(range[i].l>ed)//如果下一区间的左边界大于上一个区间的右边界,即两区间不相交
{
res++;
ed=range[i].r;//右边界更新为新区间的右边界
}
}
printf("%d",res);
return 0;
}
3、区间分组
1、思路
把所有区间按照左边界从小到大排序,
从前往后处理每个区间
判断能否将新区间放到某个现有的组中,
如果可以,就放到原有的组中,并且更新右端点,存在多个的话就随便进一个
如果不可以,就开一个新组放进去。
2、代码模板:
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1e5+10;
struct Range //结构体存区间,左右边界
{
int l,r;
bool operator < (const Range &W)const //重载运算符<
{
return l<W.l;
}
}range[N];
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)//输入每个区间的左右端点
scanf("%d %d",&range[i].l,&range[i].r);
sort(range,range+n);//按照每个区间的左边界从小到大排序
priority_queue<int,vector<int>,greater<int>> heap;//小根堆,从小到大
for(int i=0;i<n;i++)
{
Range x=range[i];
if(heap.empty()||heap.top()>=x.l)//如果队列为空或者两个区间有交点
heap.push(x.r); //新建一个分组
else //没有交点可以合成一个组
{
int t=heap.top();//暂存队头
heap.pop(); //删除队头
heap.push(x.r); //把新区间加入这一组,同时更新这一组的右端点,入队
}
}
printf("%d",heap.size());//看队列的元素有几个
return 0;
}
4、区间覆盖
1、思路
给定N个闭区间 [ai,bi]以及一个线段区间 [s,t],请你选择尽量少的区间,将指定线段区间完全覆盖,输出最少区间数。
将所有的区间按照左端点从小到大排序,设目标区间左端点为st,右端点为ed
第一步在所有包含st的区间中,选择右边界最大的区间r
然后将st更新为r,再在所有包含st区间中选择右边界最大的区间。
更新到大于等于ed时结束。
如果更新不到,就不能覆盖。
2、代码模板:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
struct Range{ //自定义结构体存每一个区间的左右边界
int l,r;
bool operator< (const Range W)const //重载运算符<
{
return l<W.l;
}
}range[N];
int main()
{
int st,ed,n;
scanf("%d %d",&st,&ed);
scanf("%d",&n);
for(int i=0;i<n;i++)//读入n个区间
scanf("%d %d",&range[i].l,&range[i].r);
int res=0;
bool succes=false;
sort(range,range+n);//按照每个区间左边界从小到大排序
for(int i=0;i<n;i++)
{
int j=i,r=-2e9;//初始右端点为负无穷
while(j<n&&range[j].l<=st)//当下一个新区间的左边界小于st时,即st可能在区间里
{
r=max(r,range[j].r);//找出最大的右端点
j++;
}
if(r<st)//当所有可能包含st的区间的右边界的最大值都没超过st
{
res=-1;//区间不能覆盖
break;
}
res++;//r>=st时,区间数加一
if(r>=ed)//如果最大的右边界大于等于ed时,就是覆盖完成了
{
succes=true;
break;
}
st=r;//还有没覆盖的,就更新st继续
i=j-1;//更新i
}
if(!succes)//如果不能覆盖
res=-1;
printf("%d",res);
return 0;
}
二、Huffman树问题
1、思路
每一次合并,达达可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和,设计出合并的次序方案,使达达耗费的体力最少,并输出这个最小的体力耗费值。
这个问题用树可以很直观的看出,深度越深的点,被加的次数就越多。
所以取最小的两个点,最优情况一定是深度最深,而且可以互为兄弟。
2、代码模板:
#include<iostream>
#include<queue>
using namespace std;
int main()
{
int n;
scanf("%d",&n);
priority_queue<int,vector<int>,greater<int>>heap;//优先队列,小根堆,从小到大排序
while(n--)
{
int x;
scanf("%d",&x);
heap.push(x);//将每堆果子入队,同时从小到大排序
}
int res=0;//答案数
while(heap.size()>1)//当有1个以上堆果子时
{
int a=heap.top();
heap.pop();
int b=heap.top();
heap.pop();
res+=a+b;//记录最小的两堆果子的和
heap.push(a+b);//将最小的两堆果子和入队
}
printf("%d",res);
return 0;
}
三、不等式问题
1、排序不等式
1、思路
有n个人排队到1个水龙头处打水,第i个人装满水桶所需的时间是ti,请问如何安排他们的打水顺序才能使所有人的等待时间之和最小?
每一个位置打水时,会让他之后的所有人等待他打水的时间
显然,越靠前的人要是打水时间越短,所有人的等待时间之和就越小
所有把所有人打水要的时间从小到大排序,然后依次打水,总时间最少。
2、代码模板:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
typedef long long ll;
int n,a[N];
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
sort(a,a+n);
ll res=0;
for(int i=0;i<n;i++)
res+=a[i]*(n-i-1);
printf("%lld",res);
return 0;
}
2、绝对值不等式
1、思路
在一条数轴上有N家商店,它们的坐标分别为A1∼AN。现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。
当前的距离和为:
我们对它进行分组,首尾分为一组
对于每一组我们可以保证xn在x1右边
所以,我们需要让仓库x尽量在更多组的中间,即中位数,
对于奇数个商店,就在最中间
对于偶数个商店,就在中间两个商店之间的任意一点。
2、代码模板:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int n,a[N];
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
sort(a,a+n);//从小到大排序
int res=0;
for(int i=0;i<n;i++)
res+=abs(a[i]-a[n/2]);//依次加上点到中间距离的位置
printf("%d",res);
return 0;
}
四、推公式
1、思路
这N头奶牛中的每一头都有着自己的重量wi以及自己的强壮程度si。风险值为一头牛它头上所有牛的总重量(不包括它自己)减去它的身体强壮程度的值,确定奶牛的排序,使得所有奶牛的风险值中的最大值尽可能的小。
方法:按照wi+si的和从小到大排,一定最小,
不让体重大的在上方,也不让可以抗很多的在上方。
2、代码模板:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=5e4+10;
typedef pair<int,int> PII;//重量+强壮程度,强壮程度
int n;
PII cow[N];
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
int x,y;
scanf("%d %d",&x,&y);
cow[i].first=x+y;
cow[i].second=y;
}
sort(cow,cow+n);//按照和排序
int res=-2e9,sum=0;//初始化最大值为负无穷,第i头牛上方牛的体重和sum=0
for(int i=0;i<n;i++)
{
int x=cow[i].first-cow[i].second;//x为第i头牛的体重
res=max(res,sum-cow[i].second);//算最大的危险程度
sum+=x;//加上体重
}
printf("%d",res);
return 0;
}
查看10道真题和解析