贪心算法(包含配套题目)已更:背包问题,不相交区间选择问题,Elephant,Packets,Minimize the Permutation,Car

一、算法概要

主要是强调当前这个状态,从这个状态里寻找最优解,从而得出最终解。

同时也引出了运用此算法的条件(前提):
如何判断一道题是否需要此算法,我们需要去证明从每个子集中解能够得出最优解(即最终解)。

此外,关于子集解的划分求,一种方式可以从数据入手,在数据中寻找特殊数据,如最大值最小值,起始值等。

二、从具体例子中体现

1.背包(含价值)问题

问题概述:现存n个物体,其中每个物体重量为Wi,每个物体的价值为Pi,在总质量不超过C的情况下尽可能使总价值高。每个物品可取部分。
输入

n C
w1 p1
w2 p2
·  ·
·  ·
wn pn

输出

最大价值k

分析:首先若考虑从最大价值的物品开始取,可能其重量也大,则最终取的总重量在C之内的物品可能少,其K值也不一定是最大;若从重量最小的取,可能其价值最小,使之更不满足K的最大化。综合考虑,可以将数据依据 Gi(价值/重量)的大小排序,优先选择Gi大的,直到价值sum=>C为止。
代码如下

#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
#define ll long long

struct thing{
    
    
	int zl;
	int jz;
	double g;
};
struct thing t[20];
bool cmp(thing x,thing y)
{
    
    
	return x.g<y.g;
}
int main(void)
{
    
    
	int n,C;
	scanf("%d%d",&n,&C);
	for(int i=1;i<=n;i++)
	{
    
    
		scanf("%d%d",&t[i].zl,&t[i].jz);
		t[i].g=(t[i].jz*1.0/t[i].zl);
	}
	sort(t,t+n,cmp);
	double sum=0;
	int G=0;
	int j;
	for(j=1;j<=n;j++)
	{
    
    
		sum+=t[j].jz;
		G+=t[j].zl;
		if(G+t[j+1].zl>=C)
		{
    
    
			j++;
			break;
		}
	}
	sum+=((C-G)*t[j].g); 
	cout<<sum;
	return 0;
}

2.不相交区间选择问题

问题:数轴上有n个开区间( a i , b i ) 。选择尽量多个区间,使得这些区间没有公共点。
分析:考虑时间长度,时间起始点。
我们需要分析两个区间a,b的关系:

  • 若a与b相交且不包含:则选择开始时间在前面的区间;
    若a与b相交且不包含
  • 若a与b是包含关系,则选择被包含的(如同选择b)
    在这里插入图片描述

代码如下(示例):

#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
#define ll long long

struct qj{
    
    
	int st;
	int end;
};
struct qj t[20];
bool cmp(qj x,qj y)
{
    
    
	return x.end<y.end;
}
int main(void)
{
    
    
	int n;
	scanf("%d",&n); 
	int cnt=0;
	for(int i=0;i<n;i++)
	{
    
    
		scanf("%d%d",&t[i].st,&t[i].end);
	}
	sort(t,t+n,cmp);
	int E=0;
	for(int i=0;i<n;i++)
	{
    
    
		if(t[i].st>E)
		{
    
    
			cnt++;
			E=t[i].end;
		}
	}
	cout<<cnt;
	return 0;
}

提示:选择贪心算法一定要确保题目能使局部最优为解

如上述问题一中,若纸币为11,5,1,总面额为15时,如果用上面算法则答案为11,1,1,1,1,但是该情况下最优解应该是5,5,5。所以不满足题意。
因此在用贪心时一定要提前确保局部解为最优解。

三,例题演练(持续补充中)

1,Elephant
问题:
An elephant decided to visit his friend. It turned out that the elephant’s house is located at point 0 and his friend’s house is located at point x(x > 0) of the coordinate line. In one step the elephant can move 1, 2, 3, 4 or 5 positions forward. Determine, what is the minimum number of steps he need to make in order to get to his friend’s house.
input:
The first line of the input contains an integer x (1 ≤ x ≤ 1 000 000) — The coordinate of the friend’s house.
output:
Print the minimum number of steps that elephant needs to make to get from point 0 to point x.
在这里插入图片描述

关键在于顺序贪心

#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
#define ll long long
int main(void)
{
    
    
	int a[5]={
    
    5,4,3,2,1};//要素1*
	int p;
	cin>>p;
	int ans=0;
	int sum=0;
	for(int i=0;i<5;i++)
	{
    
    
		while(sum<p)//体现*
		{
    
    
			sum+=a[i];
			ans++;
		}
	} 
	cout<<ans;
	return 0;
}

2,Packets
在这里插入图片描述
在这里插入图片描述

关键在于选择的顺序,我们需要从最大的6x6个数开始,再依次选择4x4,3x3,最后再去看剩余空间中可以供2x2的数,最后看1x1.

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<map>
#include<vector>

using namespace std;
#define ll long long
int a[10];
int a_[4]={
    
    0,5,3,1};//表示a[3]剩余量中可以包含2*2的盒子数 

int main()
{
    
    
	while(1)
	{
    
    
		int s=0;
		for(int i=1;i<7;i++)
		{
    
    
			scanf("%d",&a[i]);
			s+=a[i];
		}
		if(s==0) break;
		int sum=a[6]+a[5]+a[4]+(a[3]+3)/4;
		int a3s=a[3]%4; //a3s表示a[3]多出来的个数;
		int a2=a[2]-a_[a3s]-a[4]*5;//a2表示后面剩余的盒子数;
		if(a2>0) sum+=(a2+8)/9;
		int a1=a[1]-(sum*36-a[6]*36-a[5]*25-a[4]*16-a[3]*9-a[2]*4);
		if(a1>0) sum+=(a1+35)/36;
		cout<<sum<<endl;
	}
	return 0;
}

3,Minimize the Permutation
在这里插入图片描述

此题考的是置换,每个位置只能与后面的数字换一次,先从最后的数字开始换,使小数字尽量在前面,再从第一个数字换(交换的前提是该位置第一次换的时候没有调整过)

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<map>
#include<vector>

using namespace std;
#define ll long long
int a[500];
int b[500];
int main(void)
{
    
    
	int t;
	scanf("%d",&t);
	while(t--)
	{
    
    
		int n;
		scanf("%d",&n);
		for(int i=0;i<n;i++)
		{
    
    
			scanf("%d",&a[i]);
		}
		for(int i=n-1;i>0;i--)
		{
    
    
			if(a[i]<a[i-1])
			{
    
    
				int y=a[i];
				a[i]=a[i-1];
				a[i-1]=y;
				b[i-1]=1;
			}
		}
		for(int i=0;i<n-1;i++)
		{
    
    
			if(a[i]>a[i+1]&&b[i]==0)
			{
    
    
				int y=a[i];
				a[i]=a[i+1];
				a[i+1]=y;
			}
		}
		for(int i=0;i<n;i++)
		{
    
    
			b[i]=0;
			cout<<a[i]<<' ';
		}
		cout<<endl;
	}
	return 0;
}

4,Car
在这里插入图片描述

关键在于如何把每一段的速度V求取出来。
这里需要注意的是我们为确保时间t为整数,所以每段的v应该为double值,此外贪心体现在从最后一段。
(即该段速度最大的一段开始求,使t总最小,所以最后一段t‘最小为1s,所以v’=s’)。
倒推得出每一段的V(min_t)。需要注意的是该题的难度在于如何求v。例如若s1,s2,s3分别为2,3,2.则易出v3=2m/s,t3=1s;=> v3>s2,所以v2!=s2,这里求v2划重点
v2=v=(double(s)/(int(double(s-max)/v)+1));
分解v2=>double(s)是把s变小数,相当于s*1.0,
t=(int(double(s-max)/v)+1);=>s-max主要是考虑到s/v是整除的情况下,能防止t+1浪费;
=>double(s-max)/v+1,当s/v不能整除时,tmax=(s/v)+1;

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<map>
#include<vector>

using namespace std;
#define ll long long
#define max (1e-8)
int a[200001];
int s;
double v;
int k=1;
int main(void)
{
    
    
	int t;
	cin>>t;
	while(t--)
	{
    
    
		
		int n;
		cin>>n;
		ll ts=0;
		for(int i=1;i<=n;i++)
		{
    
    
			scanf("%d",&a[i]);
		}
		v=a[n]-a[n-1];
		ts=1;
	//	cout<<"v="<<v<<endl;
		for(int i=n-1;i>=1;i--)
		{
    
    
			s=a[i]-a[i-1];
			if(s<=v)
			{
    
    
				v=s;
				ts++;
			}else
			{
    
    
				ts+=(int(double(s-max)/v)+1);
				v=(double(s)/(int(double(s-max)/v)+1));
			}
			//cout<<"v="<<v<<endl;
		
		}
		
		printf("Case #%d: %d\n",k++,ts);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/VinciB/article/details/113143412