贪心小总结

介绍几种贪心题型

1.选择不相交区间:

按照结束时间从大到小排序,如果区间左端点大于当前最右点就选,否则不选。

例题:活动安排:

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int maxm=1100;
int n,cnt;
struct node
{
  int l,r;
  bool operator < (const node&s) const
  {
   return r<s.r;	
  }
}a[maxm];
int main()
{
 scanf("%d",&n);
 for(int i=1;i<=n;i++)
 {
  scanf("%d%d",&a[i].l,&a[i].r);	
 }
 sort(a+1,a+n+1);
 int tail=0;
 for(int i=1;i<=n;i++)
 {
   if(a[i].l>=tail)
   {
     tail=a[i].r;
     cnt++;
   }
 }
 printf("%d
",cnt);
 return 0;	
}

2.区间选点问题:

按照区间的结束位置从大到小排序。对于当前区间如果选的点不够,就尽量在区间末尾选点。

例题:种树

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
int n,m;
struct node
{
 int l,r,t;
 bool operator < (const node &s) const
 {
  return r<s.r;	
 }
}a[5100];
bool flag[30007];
int cnt;
int main()
{
 scanf("%d",&n);
 scanf("%d",&m);
 for(int i=1;i<=m;i++)
 {
  	scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].t);
 }
 sort(a+1,a+m+1);
 for(int i=1;i<=m;i++)
 {
   int k=0;
   for(int j=a[i].l;j<=a[i].r;j++)
   {
   	 if(flag[j])
   	 {
   	  k++;	
   	 }
   }
   if(k>=a[i].t) continue;
   for(int j=a[i].r;j>=a[i].l;j--)
   {
   	 if(!flag[j])
   	 {
   	 	cnt++;
   	 	k++;
   	 	flag[j]=1;
   	 }
   	 if(k==a[i].t)
   	 break;
   }
 }
 printf("%d
",cnt);
 return 0;	
}

3.区间覆盖问题:

将闭区间按照左端点从小到大排序。对于当前区间,左端点大于已选区间最右端,记录个数。当前区间不满足此条件时,从这些满足条件的区间中,选择右端点最远的点,再判断此时的最右端能否覆盖当前区间的左端点。能,就重复上述过程,不能说明无法覆盖整个区间长。

例题:喷水装置

#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<cstdio>
#include<queue>
using namespace std;
const int maxm=15007;
int t,n;
double l,w,head,tail;
struct node
{
 double l,r;	
}a[maxm];
int tot;
bool cmp(node a,node b)
{
 return a.l<b.l;	
}
int main()
{
 scanf("%d",&t);
 while(t--)
 {
  scanf("%d%lf%lf",&n,&l,&w);
  tot=0;
  for(int i=1;i<=n;i++)
  {
    double x,y;
    scanf("%lf%lf",&x,&y);
    if(y<=w*0.5) continue;
    double len=sqrt(y*y-(w*0.5)*(w*0.5));
    a[++tot].l=x-len;
    if(a[tot].l<(double)0) a[tot].l=0;
    a[tot].r=x+len;
  }
  sort(a+1,a+tot+1,cmp);
  if(a[1].l>(double)0)
  {
  	printf("-1
");
  	continue;
  }
  head=1;
  tail=(double)0;
  bool flag=1;
  int cnt=1,ans=0;
  for(int i=2;i<=tot;i++)
  {
   	if(a[i].l>tail)
   	{
   	   for(int j=head;j<=head+cnt-1;j++)
   	   {
   	   	  tail=max(a[j].r,tail);
   	   }
   	   if(tail<a[i].l)
   	   {
   	    flag=0;
	    printf("-1
");
	    break;
       }
       head=i;
       ans++;
       if(tail>=l)
       {
       	 flag=0;
       	 printf("%d
",ans);
       	 break;
       }
       cnt=1;
   	}
   	else if(a[i].l<=tail)
   	{
   	   cnt++;
   	}
  }
  for(int j=head;j<=head+cnt-1;j++)
  {
   tail=max(a[j].r,tail);
  }
  ans++;
  if(!flag) continue;
  if(tail<l){
    printf("-1
");
  	continue;	
  }
  printf("%d
",ans);
 }
 return 0;	
}

4.流水作业调度问题:

题目概述:

某工厂收到了n个产品的订单,这n个产品分别在A、B两个车间加工,并且必须先在A车间加工后才可以到B车间加工。某个产品在A、B两车间加工的时间分别为Ai、Bi。怎样安排这n个产品的加工顺序,才能使总的加工时间最短。这里所说的加工时间是指:从开始加工第一个产品到最后所有的产品都已在A,B两车间加工完毕的时间。

做法:Johnson算法。要使机器总的空闲时间最短,就要把在A上加工时间最短的部件先加工,这样B能在最短的空闲时间后开始工作;把在B上加工时间最短的部件放在最后加工,使得A机器用最短的时间等待B机器完工。

设Mi = min(ai , bi).

将M按照从大到小的顺序排序进行处理。对于Mi=ai,则将它排在从头开始的作业后面,若Mi = bi,则将它排在从尾部开始的作业前面.算法正确性不太会证,可自行查阅。

例题:加工生产调度

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int maxm=1100;
int n;
struct node
{
 int t1,t2;
 int id,m;
}a[maxm];
bool cmp(node a,node b)
{
 return a.m<b.m;	
}
int q[maxm],l,r;
int A[maxm],B[maxm];
int main()
{
 scanf("%d",&n);
 for(int i=1;i<=n;i++)
 scanf("%d",&a[i].t1),A[i]=a[i].t1;
 for(int i=1;i<=n;i++)
 scanf("%d",&a[i].t2),B[i]=a[i].t2;
 for(int i=1;i<=n;i++)
 {
 	a[i].id=i;
 	a[i].m=min(a[i].t1,a[i].t2);
 }
 sort(a+1,a+n+1,cmp);
 l=0;
 r=n+1;
 for(int i=1;i<=n;i++)
 {
  	if(a[i].m==a[i].t1)
  	{
  	  q[++l]=a[i].id;
  	}
  	else
  	{
	  q[--r]=a[i].id;
    }
 }
 int ans1=0,ans2=0;
 for(int i=1;i<=n;i++)
 {
   	ans1+=A[q[i]];
   	if(ans2<ans1) ans2=ans1;
   	ans2+=B[q[i]];
 }
 printf("%d
",ans2);
 for(int i=1;i<=n;i++)
 {
  if(i!=n)
  printf("%d ",q[i]);
  else
  printf("%d",q[i]);
 }
 return 0;	
}

5.带限期和罚款(得分)的单位时间任务调度

将罚款(得分)按从大到小排序处理。将当前任务放在既在期限之内,又尽量靠后的时间完成。如果不能再期限完成,根据题意如果是必须完成,就放到整个时间段尽量靠后的位置,此位置打标记。

例题:智力大冲浪

#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<iostream>
using namespace std;
const int maxm=510;
int m,n;
struct node
{
 int w,t;
 bool operator <(const node &s) const
 {
  return w>s.w;
 }
}a[maxm];
bool flag[maxm];
int main()
{
 scanf("%d",&m);
 scanf("%d",&n);
 for(int i=1;i<=n;i++)
 scanf("%d",&a[i].t);
 for(int i=1;i<=n;i++)
 scanf("%d",&a[i].w);
 sort(a+1,a+n+1);
 for(int i=1;i<=n;i++)
 {
   bool mon=1;
   for(int j=a[i].t;j>=1;j--)
   {
   	 if(flag[j]) continue;
   	 flag[j]=1;
   	 mon=0;
   	 break;
   }
   if(mon)
   {
   	 for(int j=n;j>=1;j--)
   	 {
   	  if(flag[j]) continue;
		 flag[j]=1;
		 m-=a[i].w;
		 break;
   	 }
   }
 }
 printf("%d
",m);
 return 0;	
}

6.均分纸牌问题

1.均分纸牌通用公式:({T})为总和,(ave=frac{T}{N})({S})是纸牌数组({a[i]-ave})的前缀和,(ans=sum_{i=1}^{n}left | S[i] ight |)

2.环形均分纸牌:在第K个人之后把环断开站成一行,这N个人持有的纸牌数、前缀和分别是

A[K+1]、S[K+1]-S[K],A[K+2]、S[K+2]-S[K]...A[N]、S[N]-S[K],A[1]、S[1]+S[N]-S[K]...A[K]、S[K]+S[N]-S[K] 。

由于均分之后,({S[M]=0})恒成立,所以前缀和的变化仅仅是减去({S[k]})

所以,所需最小花费:(sum_{i=1}^{n}left | S[i]-S[k] ight |)

当K取何值时上式最小?这就是“货仓选址”问题(在数轴上找一点使得该点到所有其他点的距离之和最小)法:找到大小为中位数的点,该点就是要求的点(如有两个取之间任意一点都行)

例题:[HAOI2008]糖果传递

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
typedef long long ll;
const int maxm=1e6+7;
int n;
ll sum[maxm],a[maxm],ave;
ll ans;
int main()
{
 scanf("%d",&n);
 for(int i=1;i<=n;i++)
 scanf("%lld",a+i),ave+=a[i];
 ave/=n;
 for(int i=1;i<=n;i++)
 {
  a[i]-=ave;
  sum[i]=sum[i-1]+a[i];
 }
 sort(sum+1,sum+n+1);
 int id=(n+1)/2;
 for(int i=1;i<=n;i++)
 {
  ans+=abs(sum[i]-sum[id]);
 }
 printf("%lld
",ans);
 return 0;	
} 

练习题:

1.数列极差:维护一个大根堆和一个小根堆,大根堆取出两个数合并,最后算出来的数最小;小根堆取出两个数合并,最后得出最大值。

2.数列分段:直接贪心即可,超过({M})答案就加({1})

3.线段:模型1。

4.家庭作业:模型5,因为({n,t})较大,可以进行剪枝。新开一个({vis}),表示({1-vis})已经全部占完。当期限小于它时,就直接舍去。(注意本题无要求必须做完)

5.钓鱼:借助DP思想,枚举以i池塘结束的最大钓鱼数。先预处理({t[i]})表示({1-i})池塘行走的时间.在进行枚举时,找出此次钓鱼数最多的池塘,给({sum})加上,在把池塘的鱼对应减少。当最大值为({0})时或超过时间范围,跳出与({ans})比较。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cmath>
#include <queue>
using namespace std;
const int maxm=110;
int n,h,ans;
struct node
{
 int start,cos;
 int t;	
}a[maxm];
int b[maxm];
int t[maxm];//记录路程
int t1,sum;
int find(int x)
{
  int id,maxx=-1;
  for(int i=1;i<=x;i++)
  {
    if(maxx<b[i])
    maxx=b[i],id=i;	
  }
  return id;
}
int main()
{
 scanf("%d",&n);
 scanf("%d",&h);
 h*=60;
 for(int i=1;i<=n;i++)
 scanf("%d",&a[i].start);
 for(int i=1;i<=n;i++)
 scanf("%d",&a[i].cos);
 for(int i=1;i<=n-1;i++)
 scanf("%d",&a[i].t);
 for(int i=1;i<=n;i++)
 t[i]=t[i-1]+a[i-1].t*5;
 for(int i=1;i<=n;i++)//枚举以i为终点
 {
   t1=t[i];
   sum=0;
   for(int j=1;j<=n;j++)
   b[j]=a[j].start;
   for(int j=i;;)
   {
   	 int bj=find(j);
   	 if(b[bj]<=0) break;
   	 sum+=b[bj];
   	 b[bj]-=a[bj].cos;
   	 t1+=5;
   	 if(t1+5>h) break;
   }
   ans=max(ans,sum); 
 }
 printf("%d
",ans);
 return 0;	
}

原文地址:https://www.cnblogs.com/lihan123/p/11663479.html