贪心问题
标签(空格分隔): @zhshh OI cpp 贪心
作业调度(区间不重叠覆盖问题)
特征
在一段长度内,有可选择的区间[a,b]的N段,求在这段长度内最大数目。
思路
先排序右区间,右区间相同排序左区间。
理论依据:从左往右来看,如果如果要尽可能多的安排,首先选择的应该是尽量早些结束的(对于剩下的相同的数据来说,结束早的带来的影响一定不劣于晚的。早些结束的有可能给更多的时间去结束别的)
因此排序时按照结束时间排序。而起始时间按照什么顺序排序似乎没有影响。(因为一定是从左往右扫描一遍的,选中那个一样)
例题
设有n个活动的集合E={1,2,…,n},其中每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。每个活动i都有一个要求使用该资源的起始时间si和一个结束时间fi,且si <fi。如果选择了活动i,则它在半开时间区间[si, fi)内占用资源。若区间[si, fi)与区间[sj, fj)不相交,则称活动i与活动j是相容的。也就是说,当si≥fj或sj≥fi时,活动i与活动j相容。活动安排问题就是要在所给的活动集合中选出最大的相容活动子集合。
如下代码。
#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
struct node{
int b,f;
node(){
b=f=0;//begin finish
}
}a[1050];
int n;
int cmp(node x,node y){
if(x.f!=y.f){
return x.f<y.f;
}else{
return x.b<y.b;
}
}
void init(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].b>>a[i].f;
}
}
void work(){
sort(a+1,a+n+1,cmp);
int cnt=-1;
int all=0;
for(int i=1;i<=n;i++){
if(a[i].b>=cnt){
cnt=a[i].f;
all++;
}
}
cout<<all;
}
int main(){
freopen("act.in","r",stdin);
freopen("act.out","w",stdout);
init();
work();
return 0;
}
/*
4
1 3
4 6
2 5
1 7
*/
区间种树问题(重叠覆盖重复计数多次)
特征
有重复区间,在这些重复区间内的数目被重复计算。求最小种树数目
思路
如果尽可能少,则应该优选重复尽可能多的。而且重复一定是在区间首尾。
从左往右计算,现在第一个区间尾种树。然后再计算差多少,就在尾部种多少。
例题
codevs1653
现在我们国家开展新农村建设,农村的住房建设纳入了统一规划,统一建设,政府要求每一住户门口种些树。门口路边的地区被分割成块,并被编号成1..N。每个部分为一个单位尺寸大小并最多可种一棵树。每个居民房子门前被指定了三个号码B,E,T。这三个数表示该居民想在B和E之间最少种T棵树。当然,B≤E,居民必须记住在指定区不能种多于区域地块数的树,所以T≤E-B+l。居民们想种树的各自区域可以交叉。你的任务是求出能满足所有要求的最少的树的数量,尽量较少政府的支出。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#define MX 30010
using namespace std;
struct node {
int b,e,w;
}a[MX];
int n,m;
int tr[MX];
void init(){
cin>>m>>n;
for(int i=1;i<=n;i++){
cin>>a[i].b>>a[i].e>>a[i].w;
}
}
int cmp(node x,node y){
if(y.e!=x.e){
return x.e<y.e;
}else{
return x.b<y.b;
}
}
void work(){
int tot=0;
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++){
for(int j=a[i].e;j>=a[i].b;j--){
if(tr[j]==1){
a[i].w--;
}
}
for(int j=a[i].e;j>=a[i].b;j--){
if(tr[j]==0 && a[i].w>0){
a[i].w--;
tr[j]=1;
tot++;
}
if(a[i].w==0){
break;
}
}
}
// for(int i=1;i<=m;i++){
// printf("%3d",i);
// }
// cout<<endl;
// for(int i=1;i<=m;i++){
// printf(" %c"," *"[tr[i]]);
// }
cout<<tot;
}
int main(){
init();
work();
return 0;
}
喷水问题(区间连续覆盖问题(多端区间连续覆盖到一定长度,求最小数目))
特征
多个固定可选的区间。选择最少数目覆盖连续的一段长度。
思路
\(1. 从左往右排序右端点。\)
\(2. l=0\)
\(3. 找到l_i < l且右端点最长的i_0\)
\(4. l=r_{i_0},重复3 4,直到右端点覆盖\)
例题
一块长l,宽w的长方形草坪,在其中心线的不同位置处装有n个点状的喷水装置。每个喷水装置i可将以它为中心,半径为ri的圆形区域润湿。请选择尽量少的喷水装置,把整个草坪全部润湿。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
int n,l,w,tot=0;
struct node{
double mid;
double r;
int used;
double left,right;
}a[10010];
void init(){
n=l=w=tot=0;
memset(a,0,sizeof(a));
int temp,t2;
cin>>n>>l>>w;
for(int i=1;i<=n;i++){
cin>>t2>>temp;
tot++;
a[tot].mid=t2;
if(temp*1.0>=w*1.0/2){
a[tot].r=sqrt(1.0*temp*temp-1.0*w*w/4);
a[tot].left=a[tot].mid-a[tot].r;
a[tot].right=a[tot].mid+a[tot].r;
}else{
tot--;
}
}
return ;
}
//int cmp(node a,node b){
// if(a.right!=b.right){
// return a.right<b.right;
// }else{
// return a.left
// }
//}
void work(){
// sort(a+1,a+tot+1,cmp);
double fns=0;
int mx=0;
double mxx=0;
int ans=0;
while(ans!=tot){
mx=0;
mxx=fns;
for(int i=1;i<=tot;i++){
if(!a[i].used && a[i].left<=fns){
if(mxx<=a[i].right){
mx=i;
mxx=a[i].right;
}
}
}
if(mx!=0){
a[mx].used=1;
// cout<<"mark"<<mx<<endl;
fns=mxx;
ans++;
}
if(fns>=l){
cout<<ans<<endl;
return ;
}else{
if(ans==tot || (ans<tot && mx==0)){
cout<<-1<<endl;
return ;
}
}
}
if(fns>=l){
cout<<ans<<endl;
}else{
if(ans==tot){
cout<<-1;
return ;
}
}
}
int main(){
int NNN;
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
cin>>NNN;
for(int i=1;i<=NNN;i++){
init();
work();
}
return 0;
}
家庭作业(相同时长任务,收益和截止期限不同。求最大收益)
特征
任务时长相同。但是收益不同,且截止日期不同。合理安排时间,得到最大收益。
思路
按照收益排序。然后优选收益最大的,从后往前安排(尽量少挤占空间),如果安排不下就放弃换任务。
其中如果要有优化,就是使用链表(数组模拟)快速跳过已经安排的位置。
例题
老师在开学第一天就把所有作业都布置了,每个作业如果在规定的时间内交上来的话才有学分.每个作业的截止日期和学分可能是不同的
.例如如果一个作业学分为10,要求在6天内交,那么要想拿到这10学分,就必须在第6天结束前交.
每个作业的完成时间都是只有一天.例如,假设有7次作业的学分和完成时间如下:
1 2 3 4 5 6 7
期限 1 1 3 3 2 2 6
学分 6 7 2 1 4 5 1
最多可以获得15学分,其中一个完成作业的次序为2,6,3,1,7,5,4,注意可能还有其他方法.
你的任务就是找到一个完成作业的顺序获得最大学分.
#include <iostream>
#include <cstdio>
#include <algorithm>
#define MX 1000100
using namespace std;
struct node{
int w;
int e;
}a[MX];
int pre[MX]={0};
int pos[MX]={0};
int n,m;
int operator < (const node a,const node b){
if(a.w!=b.w){
return a.w>b.w;
}else{
return a.e<b.e;
}
}
void init(){
cin>>n;
for(int i=1;i<=n;i++){
// cin>>a[i].e>>a[i].w;
scanf("%d%d",&a[i].e,&a[i].w);
m=max(m,a[i].e);
}
for(int i=0;i<=m+1;i++){
pre[i]=i-1;
pos[i]=i+1;
}
}
void work(){
int ans=0;
int mxj=0,mxv=0;
sort(a+1,a+n+1);
for(int i=1;i<=n;i++){
int v=a[i].e;
while(pre[v]!=-1){
}
if(v==-1){
continue;
}else{
pre[pos[v]]=pre[v];
pos[pre[v]]=pos[v];
}
}
cout<<ans;
}
int main(){
init();
work();
return 0;
}
钓鱼(。。。描述不出来)
特征
在同一个点可以长时间钓鱼(单位时间收益递减)或者通过一定路程(N个单位时间损耗)换一个钓鱼点
钓鱼点线性排列,一个挨着一个。
思路
分情况贪心。。依次不走/走一个/走两个,先刨去走路时间(常数)。。。。。进行贪心,每次选取收益最大值并更新这一组数,如果最大收益为负数即可停止。之后依次比较每次最大收益取最大值
例题
在一条水平路边,有n(2<=n<=100)个钓鱼湖,从左到右编号为1、2、…、n。佳佳有H(1<=H<=20)个小时的空于时间,他希望利用这个时间钓到更多的鱼。他从1出发,向右走,有选择的在一些湖边停留一定的时间(是5分钟的倍数)钓鱼。最后在某一个湖边结束钓鱼。佳佳从第i个湖到第i+1个湖需要走5*Ti分钟路,还测出在第i个湖停留,第一个5分钟可以钓到Fi条鱼,以后每再钓5分钟,鱼量减少Di。为了简化问题,佳佳假定没有其他人钓鱼,也没有其他因素影响他钓到期望数量的鱼。请编程求出佳佳最多能钓鱼的数量。
#include <iostream>
#include <queue>
#include <algorithm>
using namespace std;
int n,h;
int a[110],b[110],c[110];
struct node{
int x,d;//number && decrease
void dec(){
x-=d;
}
node(int a,int b){
x=a;
d=b;
}
};
int operator <(node a,node b){
return a.x<b.x;
}
priority_queue<node>q;
void init(){
cin>>n>>h;
h*=12;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
cin>>b[i];
}
for(int i=2;i<=n;i++){
cin>>c[i];
}
}
int workone(int x){//if goto x pools ,spend this time can do much is
int tme=h;
int num=0;
for(int i=1;i<=x;i++){
tme-=c[i];
}
for(int i=1;i<=x;i++){
q.push(node(a[i],b[i]));
}
// cout<<"qwe"<<tme<<endl;
while(tme>0){
node temp=q.top();
q.pop();
if(temp.x<=0){
break;
}
num+=temp.x;
temp.dec();
q.push(temp);
tme--;
}
return num;
}
void work(){
int qwe=0;
for(int i=1;i<=n;i++){
qwe=max(workone(i),qwe);
}
cout<<qwe;
}
int main(){
init();
work();
}