信息奥赛c++学习笔记——贪心算法

一,算法
在当前状态下找出局部最优解,转化为全局最优解。
二,活动安排问题
1.问题描述:给定n个开区间(ai,bi),在给定范围内尽量选择多个区间,使得这些区间两两没有公共点。
2.算法:按照结束时间bi从小到大排序,然后按照排序顺序依次考虑每个活动,如果没有和已选活动冲突,就选,否则不选。
3.代码

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
struct qing{
	int a,b;
	bool operator<(const qing &x)const{
		return b<x.b;
	}
}f[1000001];
int main(){
	int n,ans=1,p;
	cin>>n;
	for(int i=1;i<=n;i++){
		scanf("%d%d",&f[i].a,&f[i].b);
	}
	sort(f+1,f+n+1);
	p=f[1].b;
	for(int i=2;i<=n;i++){
		if(f[i].a>=p){
			ans++;
			p=f[i].b;
		}
	}
	cout<<ans;
}

三,区间选点问题
1.问题描述:给定n个闭区间[ai,bi],在数轴上选尽量少的点,使得第i个区间内至少有vi个点(不同区间内的点可以是同一个)
2.算法:按区间结束位置从小到大排序,依次处理每个区间
统计该区间i内被覆盖的点的个数,如果个数大于vi,则continue;否则从区间末尾开始选择元素并覆盖它。从区间末尾倒推选取元素直到覆盖元素等于vi;
为何从区间末尾开始选择元素?如图,如果选灰色点,移动到黑色点会更优
在这里插入图片描述
3…代码


#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
int f[1000001],ans;//f[i]用于判断第i个点是否被覆盖 
struct node{
	int s,e,v;
	bool operator<(const node &x)const{
		return e<x.e;
	}
}a[1000001];
int main(){
	int h,n;//h无意义,题目具体需求 
	cin>>h>>n;
	for(int i=1;i<=n;i++){
		scanf("%d%d%d",&a[i].s,&a[i].e,&a[i].v);
	}
	sort(a+1,a+1+n);
	for(int i=1;i<=n;i++){
		int k=0;
		for(int j=a[i].s;j<=a[i].e;j++){
			if(f[j]) k++;
		}
		if(k>=a[i].v) continue;
		else{
			for(int j=a[i].e;j>=a[i].s;j--){
				if(!f[j]){
					f[j]=1;
					k++;
					ans++;
					if(k==a[i].v) break;
				}
			}
		}
	}	
	cout<<ans;
}

四.区间覆盖问题
1.题目描述:给定n个闭区间[ai,bi],选择最少的区间覆盖一条指定线段[s,t]
2.算法:将所有的区间按左端点从小到大排序,依次处理每个区间,每次选择覆盖点了点s的区间中右端点坐标最大的一个,并将s更新为该区间的右端点坐标,直到选择的区间已经包含了t为止。
例题(喷水装置):长 L 米,宽 W 米的草坪里装有 n 个浇灌喷头。每个喷头都装在草坪中心线上(离两边各 W/2 米)。我们知道每个喷头的位置(离草坪中心线左端的距离),以及它能覆盖到的浇灌范围。
请问:如果要同时浇灌整块草坪,最少需要打开多少个喷头?
在这里插入图片描述
3.代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
int read()                      //读入优化 
{
    char ch=getchar();
    int a=0,x=1;
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') x=-x;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        a=(a<<3)+(a<<1)+(ch-'0');
        ch=getchar();
    }
    return a*x;
}
int t,n,l,w;
int pos,R,ans,cnt;
struct water                      
{
    double l,r;                 //l是装置实际覆盖区间的左端点,r是右端点 
}a[15001];
int cmp(water x,water y)        //按照左端点从大到小排序 
{
    return x.l<y.l;
}
int main()
{
    t=read();                   //t组数据 
    for(int i=1;i<=t;i++)
    {
        n=read();      
        l=read();
        w=read();
        cnt=0;                  //cnt是有用的装置的个数,条件为r>w/2 
        for(int j=1;j<=n;j++)
        {
            pos=read();         //装置圆心距区间最左端的距离 
            R=read();
            if(R<=w/2) continue; 
            cnt++;
            a[cnt].l=pos-sqrt(R*R-(w/2.0)*(w/2.0));
            a[cnt].r=pos+sqrt(R*R-(w/2.0)*(w/2.0));
        }
        sort(a+1,a+1+cnt,cmp);
        double s=0,q;           //我们已经将区间覆盖了s米 
        int k=1,flag=0,ans=0;
        while(s<l)                       //覆盖不满就一直找喷水装置 
        {
            ans++;
            q=s;
            for(;a[k].l<=q&&k<=cnt;k++)    //在s左端找到一个右端点最大的值 
               if(a[k].r>s) s=a[k].r;      //让s更新为最大的r 
            if(s==q&&s<l) {flag=1;cout<<0<<endl;break;}  //判断无解 
        }
        if(flag==0) cout<<ans<<endl;
    }
    return 0;
}

五.机器加工问题
1.问题描述:有n个产品要在两台机器A,B上加工,每个产品都要先花费时间ai在A上加工,再花费时间bi在B上加工,确定n个产品的加工顺序,使得从第一个产品在A上加工到第n个产品在B上加工为止花费的总时间最短。
2.算法:求一个加工顺序使得加工总时间最短,就是让机器的空闲时间最短。一旦A机器开始加工,则A机器将会不停的进行作业,关键是B机器在加工过程中可能要等待A机器。很明显第一个产品机器在A上加工时,B机器必须要等待,最后一个产品在B上加工时,A机器也必须等待A机器完工。
大胆猜想,要使得机器总空闲时间最短,就要把在A上加工时间最短的产品最先加工,这样使得B机器等待A机器的时间最短,能够在最短的空闲时间内开始加工;把在B机器上加工时间最短的的产品放在最后加工,这样使得A机器等待B完工的时间最短。于是得到贪心策略:
设Mi=min{ai,bi};得到每个产品是在A还是B上的加工时间更短。
将M从小到大排序,然后从第一个开始依次处理,若Mi=ai,则产品i在A上加工时间小于B上加工时间,所以将它排在从头开始的作业后面,若Mi=bio,则产品i在B上的加工时间小于A上的加工时间,所以将它排在从尾开始的作业前面。
举例
(a1, a2, a3, a4, a5)=(3, 5, 8, 7, 10)
(b1, b2, b3, b4, b5)=(6, 2, 1, 4, 9)
则(m1, m2, m3, m4, m5)=(3, 2, 1, 4, 9 )
排序之后为:(m3, m2, m1, m4, m5) 
处理m3,因为m3=B3,所以m3安排在后面(,,,,3);
处理m2,因为m2=B2,所以m2安排在后面(,,,2,3); 
处理m1,因为m1=A1,所以m1安排在前面(1,,,2,3); 
处理m4,因为m4=B4,所以m4安排在后面(1,,4,2,3);
处理m5,因为m5=B5,所以m5安排在后面(1,5,4,2,3)。
从而得到加工的顺序1,5,4,2,3。计算出最短的加工时间34。
3.代码

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
int n, ans[10001],a[10001],b[10001],m[10001],s[10001];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++) cin>>b[i];
	for(int i=1;i<=n;i++){
		m[i]=min(a[i],b[i]);
		s[i]=i;
	}
	for(int i=1;i<=n-1;i++){
		for(int j=i+1;j<=n;j++){
			if(m[i]>m[j]){
				swap(m[i],m[j]);
				swap(s[i],s[j]);//如果直接sort这一步无法进行,所以选用交换排序 
			}
		}
	}
	int k=0,t=n+1;
	for(int i=1;i<=n;i++){
		if(m[i]==a[s[i]]) ans[++k]=s[i];
		else ans[--t]=s[i];
	}
	int t1=0,t2=0;
	for(int i=1;i<=n;i++){
		t1+=a[ans[i]];
		if(t2<t1) t2=t1;
		t2+=b[ans[i]];
	}
	cout<<t2<<endl;
	for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
}

六.带限期和罚款的单位时间任务调度
1.问题描述:有n个任务,每个任务都需要1个单位时间执行,任务i的截止时间di表示要求任务i在时间di之前必须完成,否则罚款wi。确定所有任务的执行顺序,使得惩罚最少。
2.算法:要使罚款最少,必然优先完成wi值较大的任务,因此,按照wi从大到小排序,依次处理每个任务,若有时间可以安排,则放在最晚的时间点上;否则放在最后的空位上。
为何放在最晚时间点上?
因为越往前的时间点,可以执行的任务数越多,换句话说,越前面的时间越珍贵,为了避免重复,应将当前可执行的任务放在限制时间前最晚的时间点上。
例题:智力大冲浪
三.代码

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int m,n,f;
bool  h[100001];
struct node{
	int a,b;//任务期限与罚款金额
	bool operator<(const node &x)const{
		return b>x.b;
	} 
}s[1000001];
int main(){
	cin>>m>>n;
	for(int i=1;i<=n;i++){
		scanf("%d",&s[i].a);
	}
	for(int i=1;i<=n;i++){
		scanf("%d",&s[i].b);
	}
	sort(s+1,s+1+n);
	for(int i=1;i<=n;i++){
		f=0;
		for(int j=s[i].a;j>=1;j--){
			if(!h[j]){
				f=1;
				h[j]=1;
				break;
			}
		}
		if(!f) m-=s[i].b;
	}
	cout<<m;
}

That’s all.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值