贪心问题一般证明都比较困难,因此在解这些问题时可以先假设再证明思路是否正确。下面是比较常见的贪心区间问题。
校门外的树
某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米。
我们可以把马路看成一个数轴,马路的一端在数轴0的位置,另一端在L的位置;数轴上的每个整数点,即0,1,2,……,L,都种有一棵树。
由于马路上有一些区域要用来建地铁。
这些区域用它们在数轴上的起始点和终止点表示。
已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。
现在要把这些区域中的树(包括区域端点处的两棵树)移走。
你的任务是计算将这些树都移走后,马路上还有多少棵树。
输入格式
输入文件的第一行有两个整数L和M,L代表马路的长度,M代表区域的数目,L和M之间用一个空格隔开。
接下来的M行每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标。
输出格式
输出文件包括一行,这一行只包含一个整数,表示马路上剩余的树的数目。
数据范围
1≤L≤10000,
1≤M≤100
输入样例:
500 3
150 300
100 200
470 471
输出样例:
298
解法:
这道题有两种解法,一种是利用差分的思想,将区间内的树的权值都加1,最后统计一下多少树的权值是0即可。
另一种方法是区间合并,利用贪心的思想合并区间,每次计算合并后的总区间,最后减去这些区间即可。
这道题暴力解法应该也能过…
差分解法
#include<cstdio>
#include<algorithm>
#include<cstring>
const int N=1e4+10;
int q[N];
int b[N];
void add(int x,int y)
{
b[x]+=1;
b[y+1]-=1;
}
int main()
{
int l,m;
int ans=0;
scanf("%d%d",&l,&m);
while(m--)
{
int x,y;
scanf("%d%d",&x,&y);
x++;
y++;
add(x,y);
}
for(int i=1;i<=l+1;i++)
{
q[i]=q[i-1]+b[i];
if(q[i]==0) ans++;
}
printf("%d\n",ans);
return 0;
}
区间合并
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e4+10;
struct node{
int x,y;
};
node s[N];
bool cmp(node a,node b)
{
return a.x<=b.x;
}
int main()
{
int l,m;
int res=0;
scanf("%d%d",&l,&m);
for(int i=0;i<m;i++)
{
scanf("%d%d",&s[i].x,&s[i].y);
if(s[i].x<0) s[i].x=0;
if(s[i].y>l) s[i].y=l;
}
sort(s,s+m,cmp);
int st=-2e9,ed=-2e9-1;
for(int i=0;i<m;i++)
{
if(s[i].x<=ed) ed=max(s[i].y,ed);
else{
res+=ed-st+1;
st=s[i].x;
ed=s[i].y;
}
}
res+=ed-st+1;
printf("%d\n",l-res+1);
return 0;
}
区间选点
给定N个闭区间[ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。
输出选择的点的最小数量。
位于区间端点上的点也算作区间内。
输入格式
第一行包含整数N,表示区间数。
接下来N行,每行包含两个整数ai,bi,表示一个区间的两个端点。
输出格式
输出一个整数,表示所需的点的最小数量。
数据范围
1≤N≤1e5,
−1e9≤ai≤bi≤1e9
输入样例:
3
-1 1
2 4
3 5
输出样例:
2
**解法:**右端点排序,遍历所有端点即可。
代码如下:
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e5+10;
struct node{
int x,y;
};
node s[N];
bool cmp(node a,node b)
{
return a.y<=b.y;
}
int main()
{
int n,ans=0;
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d%d",&s[i].x,&s[i].y);
int id=-2e9;
sort(s,s+n,cmp);
for(int i=0;i<n;i++)
{
if(s[i].x>id){
ans++;
id=s[i].y;
}
}
printf("%d\n",ans);
return 0;
}
最大不相交区间数量
给定N个闭区间[ai,bi],请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。
输出可选取区间的最大数量。
输入格式
第一行包含整数N,表示区间数。
接下来N行,每行包含两个整数ai,bi,表示一个区间的两个端点。
输出格式
输出一个整数,表示可选取区间的最大数量。
数据范围
1≤N≤1e5,
−1e9≤ai≤bi≤1e9
输入样例:
3
-1 1
2 4
3 5
输出样例:
2
解法
这个题跟有一道活动安排的题是一样的,同样右端点排序,遍历所有断电即可。
代码如下:
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e5+10;
struct node{
int x,y;
};
node s[N];
bool cmp(node a,node b)
{
return a.y<=b.y;
}
int main()
{
int n,ans=0;
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d%d",&s[i].x,&s[i].y);
int id=-2e9;
sort(s,s+n,cmp);
for(int i=0;i<n;i++)
{
if(s[i].x>id){
ans++;
id=s[i].y;
}
}
printf("%d\n",ans);
return 0;
}
区间分组
给定N个闭区间[ai,bi],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。
输出最小组数。
输入格式
第一行包含整数N,表示区间数。
接下来N行,每行包含两个整数ai,bi,表示一个区间的两个端点。
输出格式
输出一个整数,表示最小组数。
数据范围
1≤N≤1e5,
−1e9≤ai≤bi≤1e9
输入样例:
3
-1 1
2 4
3 5
输出样例:
2
解法
这道题开始的时候想的是将其转化为上面的最大不相交区间数量问题,可以得出正确结果,不过一些数据会超时,下面的这种解法比较好,通过堆(优先队列)时间复杂度为
n
l
o
n
g
n
n\ long\ n
n long n可以过
代码如下:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=1e5+10;
struct node{
int x,y;
};
node s[N];
bool cmp(node a,node b)
{
return a.x<=b.x;
}
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d%d",&s[i].x,&s[i].y);
sort(s,s+n,cmp);
priority_queue<int,vector<int>,greater<int> >q;//从小到大排序
for(int i=0;i<n;i++)
{
if(q.empty()||q.top()>=s[i].x)
q.push(s[i].y);
else
{
q.pop();
q.push(s[i].y);
}
}
printf("%d\n",q.size());
return 0;
}
区间覆盖
给定N个闭区间[ai,bi]以及一个线段区间[s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。
输出最少区间数,如果无法完全覆盖则输出-1。
输入格式
第一行包含两个整数s和t,表示给定线段区间的两个端点。
第二行包含整数N,表示给定区间数。
接下来N行,每行包含两个整数ai,bi,表示一个区间的两个端点。
输出格式
输出一个整数,表示所需最少区间数。
如果无解,则输出-1。
数据范围
1≤N≤1e5,
−1e9≤ai≤bi≤1e9,
−1e9≤s≤t≤1e9
输入样例:
1 5
3
-1 3
2 4
3 5
输出样例:
2
分析:
这道题先用贪心的思想左端点排序,再用双指针,遍历,时间复杂度应该为
O
(
n
)
O(n)
O(n)
代码如下:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=1e5+10;
struct node{
int x,y;
};
node s[N];
bool cmp(node a,node b)
{
return a.x<=b.x;
}
int main()
{
int n;
int st,ed;
bool f=0;
int res=0;
scanf("%d%d",&st,&ed);
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d%d",&s[i].x,&s[i].y);
sort(s,s+n,cmp);
for(int i=0;i<n;)
{
int j=i,r=-2e9;
while(j<n&&s[j].x<=st)
{
r=max(r,s[j].y);
j++;
}
if(r<st) break;
res++;
st=r;
if(r>=ed){
f=1;
break;
}
i=j;
}
if(!f) res=-1;
printf("%d\n",res);
return 0;
}
贪心问题难以证明,不过常见的贪心方向不多,最好就是多做题。