2.2 贪心算法

 贪心算法

    贪心算法就是遵循某种规则,不断贪心的选取当前最优策略的算法设计方法。

 

题一、硬币问题

    有1元、5元、10元、50元、100元、500元的硬币各C1,C5,C10,C50,C100,C500枚。现在要用这些硬币来支付A元,最少需要多少枚硬币?假设本题至少存在一种支付方案。 
    限制条件: 
           0<=C1,C5,C10,C50,C100,C500<=10^9
           0<= A <= 10^9

          输入:C1 = 3   C2 = 2     C10 = 1    C50 = 3    C100 = 0      C500 = 2    A = 620 

          输出: 6(500元硬币1枚,50元硬币2枚,10元硬币1枚,5元硬币2枚,合计6枚) 

         解题思路:为了尽量地减少硬币的数量,我们首先得尽可能多地使用500元硬币,剩余部分尽可能多地使用100元硬币,剩余部分尽可能多地使用50元硬币,剩余部分尽可能多地使用10元硬币,再剩余部分尽可能多地使用5元硬币,最后的部分使用1元硬币支付。

#include <stdio.h>
#include <stdlib.h>

//void min(int i,int j)
//{
//	if(i>j)
//		return j;
//	if(j>i)
//		return i;
//}

void solve(int A){	
	const int V[6] = {1,5,10,50,100,500};
	const int C[6] = {100,100,100,100,100,100};
	int ans=0;//硬币枚数 
	
	for(int i=5;i>=0;i--)
	{
		//int t=min(A/V[i],C[i]);
		int t = A/V[i];
		A -= t*V[i];
		ans += t;
	}
	
	printf("%d\n",ans);
}

int main(){
	int A;
	scanf("%d",&A);
	void solve(int A);
	
	solve(A);
}

 

 

题二、区间问题

问题描述:

    有n项工作,每项工作分别在si开始,ti结束。对每项工作,你都可以选择参加或不参加,但选择了参加某项工作就必须至始至终参加全程参与,即参与工作的时间段不能有重叠(即使开始的时间和结束的时间重叠都不行)。

限制条件:

    1<=n<=100000

    1<=si<=ti,=10^9

样例:

    输入

        n = 5, s= {1,2,4,6,8}, t={3,5,7,9,10}

    输出

       3(选择工作1, 3, 5)

解题分析:

    对这个问题,如果使用贪心算法的话,可能有以下几种考虑:

    (1)、每次选取开始时间最早的;

    (2)、每次选取结束时间最早的;

    (3)、每次选取用时最短的;

    (4)、在可选工作中,每次选取与最小可选工作有重叠的部分;

只有(2)是正确没有反例的;

#include <stdio.h>
#include <stdlib.h>

const int MAX_N = 100000;
int N = 5,S[MAX_N] = { 1,2,4,6,8 }, T[MAX_N] = { 3,5,7,9,10 };
//N项工作,S为开始时间,T为结束时间

//pair<int, int> itv[MAX_N];//用于对工作排序的pair数组
 pair 默认对first升序,当first相同时对second升序;
//

 
void sort(int S[],int T[])
{
	 int j=0;
	 int sw0,sw1;
 
	 while(j<5)
	 {
		  for(int i=0;i<4;i++)
		 {		 	
		 	if(T[i] > T[i+1])
		 	{	
		 		sw0 = T[i];
		 		sw1 = S[i];
		 		
		 		T[i] = T[i+1];
		 		S[i] = S[i+1];
		 		
		 		T[i+1] = sw0;
		 		S[i+1] = sw1;
			 }
			 
			 if(T[i] == T[i+1])
			 {
			 	if(S[i] < S[i+1])
			 	{
			 		sw0 = T[i];
		 			sw1 = S[i];
		 		
		 			T[i] = T[i+1];
		 			S[i] = S[i+1];
		 		
		 			T[i+1] = sw0;
		 			S[i+1] = sw1;
				 }
			 }
		 }
		 j++; 
	}
	int ans = 0, t = 0;//t是最后所选工作结束的时间
    /*
    结束时间小于下一个最先结束可执行时间,
    */
    for (int i = 0; i < N; i++) 
    {
        if (t < S[i])//判断区间是否重叠  
        {
            ans++;
            t = T[i];
        }
    }
    //cout << ans << endl;
    printf("%d",ans);
	
	
	
 } 

void solve()
{
//    for (int i = 0; i < N; i++)
//    {
//        itv[i].first = T[i];//开始时间 
//        itv[i].second = S[i];//结束时间 
//    }
    /*
    sort:首先根据first进行排序,first数据相同根据second排序【重点(注意sort执行的过程)】
    按照由小及大进行排序,即按照结束时间最小进行排序,结束时间相同按照开始时间最小进行排序。
    */
    sort(S, T);
//    int ans = 0, t = 0;//t是最后所选工作结束的时间
//    /*
//    结束时间小于下一个最先结束可执行时间,
//    */
//    for (int i = 0; i < N; i++) 
//    {
//        if (t < T[i])//判断区间是否重叠  
//        {
//            ans++;
//            t = S[i];
//        }
//    }
//    //cout << ans << endl;
//    printf("%d",ans);
}
int main() {
    solve();
    
    return 0;
}

 

 

题三、Best Cow Line(POJ 3617)

问题描述:

    给定长度为N的字符串S,要构造一个长度为N的字符串T。期初,T是一个空串,随后反复进行下列任意操作:

    1>从S的头部删除一个字符,加到T的尾部; 
    2>从S的尾部删除一个字符,加到T的尾部。

目标是要构造字典序尽可能小的字符串T。

举例:

    比如当N=6,S=”ACDBCB”时,程序应输出ABCBCD。

思路: 
将S反转后的字符串定为S’,比较S和S’的字典序,如果S较小则从S开头取字符加到T的末尾,反之从S末尾取字符加到T的末尾。字典序相同时两者等价,取哪个都行。

字典排序(lexicographical order)是一种对于随机变量形成序列的排序方法。其方法是,按照字母顺序,或者数字小大顺序,由小到大的形成序列。

#include <stdio.h>
#include <stdlib.h>

char T[10],S[10],K[10];

int main()
{
	int n,k=0,a=0,b=0;
	while(scanf(" %d",&n) != EOF)
	{
		int L=n;
		for(int i=0;i<n;i++)
		{
			scanf(" %c",&T[i]);
		}
		
		for(int j=n-1;j>=0;j--)
		{
			S[j]=T[k];
			k++;
		}
	
		//T[m]是正,S[a]是反 
		for(int m=0;m<=n;m++)
		{
			
			if(T[m]>S[a])
			{
				K[b]=S[a];
				a++;
				b++; 
				m--;
				n--;
			}
			
			if(T[m]==S[a])
			{
				K[b]=S[a];
				a++;
				b++;
				m--;
				n--;
			}
			
			if(T[m]<S[a])
			{
				K[b]=T[m];
				b++;
			}
		}
		
		for(int g=0;g<L;g++)
		{
			printf("%c",K[g]);
		}

	}
	return 0;
	
}

 

题四、Saruman's Army(POJ 3069)

问题描述:

    直线上有n个点,点i的位置是Xi。从这n个点中选择若干个,给它们加上标记。对每一个点,其距离为R以内的区域里必须有带标记的点(本身为带有标记的点,可以认为与其距离为0的地方有一个带有标记的点)。在满足这个条件的情况下,希望能为尽可能少的点添加标记。求最少要有多少个点被加上标记。

限制条件:

  • 1<= N <=1000
  • 0<= R <=1000
  • 0<= Xi <=1000

/*
6
10
1,7,15,20,30,50
*/
#include <stdio.h>
#include <stdlib.h>


void solve(int a[],int N,int R)
{
	int i=0;
	int ans=0;
	
	while (i < N)
	{
		int start1 = a[i++];//标记一个点 
		//一直向右前进直到距离标记点距离大于R 
		while(i < N && start1 + R >= a[i])
		{
			i++;
		}
		
			
		int start2 = a[i-1];//标记点 
		//一直向右前进直到距离标记点距离大于R 
		while (i < N && start2 + R >= a[i])
		{
			i++;
		}
		ans++;
	}
	printf("%d",ans);
}

void sort()
{
}


int main()
{
	int N,R;
	int a[100];
	scanf("%d",&N);
	scanf("%d",&R);

	if(N>=1 && R>=0)
	{
		for(int i=0;i<N;i++)
		{
			scanf("%d",&a[i]);
		}
		

		solve(a,N,R);
	}
	
	return 0;
} 

 

题五、Fence Repair( POJ 3253)

问题描述

     农夫约翰为了修理栅栏,要将一块很长的木板切割成N块。准备切成的木板长度为L1、L2、···、LN,未切割之前木板的长度恰好为切割后木板长度的总和。每次切割木板时,需要的开销为这块木板的长度。例如长度为21 的木板要切成长度为5、8、8的三块木板。长21的木板切成长为13和8的板时,开销为21.再将长度为13的板切成长度为5和8 的板时,开销是13.于是合计开销为34。请求出按照目标要求将木板切割完最小的开销是多少。(1<=N<=20000,0<=Li<=50000) 
输入样例: 
    N=3,L=(8,5,8) 
输出 
    34

解题思路:

    我们首先试图分割一块长为15的木板:

    

    这里的每一个叶子节点对应了切割出的一块块木板,叶子节点的深度就对应了为了得到对应木板所需的切割次数,开销的合计就是各叶子节点的

    木板的长度 X 节点的深度

    于是可知,此时的最佳切割方法首先应该具有如下性质:

         最短的板与次短的板的节点应当是兄弟节点

    对于最优解来讲,最短的板应当是深度最大的叶子节点之一。所以与这个叶子节点同一深度的兄弟节点一定存在,并且由于同样是最深的叶子节点,所以应该对应于次短的板。

/*
3
8 5 8 
*/ 
#include<bits/stdc++.h>
using namespace std;
const int max_n=20000;
int l[max_n+1];
int n;
int ans=0;
int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        cin>>l[i];
    }

    //计算到木板为1块为止
    while(n>1)
    {
        //求出最短的板mii1和次短的板mii2
        int mii1=0,mii2=1;
        if(l[mii1]>l[mii2]) swap(mii1,mii2);
        for(int i=2;i<n;i++)
        {
            if(l[i]<l[mii1])
            {
                mii2=mii1;
                mii1=i;
            }
            else if(l[i]<l[mii2])
            {
                mii2=i;
            }
        }

        //合并两板
        int combine=l[mii1]+l[mii2];
        ans+=combine;

        //mii1的位置放合并板,mii2的位置放最后一块板
        if(mii1==n-1) swap(mii1,mii2);
        l[mii1]=combine;
        l[mii2]=l[n-1];
        n--;
    }
    cout<<ans<<endl;
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
贪心算法是一种基于贪心思想的算法,它在每一步选择中都采取当前状态下最优的选择,从而希望最终得到全局最优解。贪心算法通常可以用来解决一些最优化问题,比如最小生成树、背包问题、最短路径等。 贪心算法的实现步骤一般如下: 1. 定义问题的贪心策略。 2. 根据贪心策略,选择当前状态下的最优解。 3. 更新问题的状态,继续步骤 2 直到达到终止条件。 需要注意的是,贪心算法并不是所有问题都能使用的算法。对于某些问题,贪心算法可能得到的不是全局最优解,而是局部最优解。因此,在使用贪心算法时,需要保证问题具有贪心选择性质和无后效性质。 下面以一个简单的例子来说明贪心算法的应用。 给定一个数组,每个素表示一个活动的结束时间和开始时间。你作为一个主办人,需要在这些活动中选择尽可能多的活动进行安排,使得不同的活动之间不会产生时间冲突。求最多能安排多少个活动。 示例代码: ```csharp public class Activity { public int start; public int end; public Activity(int start, int end) { this.start = start; this.end = end; } } public int MaxActivities(Activity[] activities) { int count = 0; int currentEnd = 0; Array.Sort(activities, (a, b) => a.end - b.end); foreach (Activity activity in activities) { if (activity.start >= currentEnd) { count++; currentEnd = activity.end; } } return count; } ``` 在这个示例代码中,我们定义了一个 `Activity` 类来表示活动的开始时间和结束时间。然后,我们通过贪心策略来选择活动,即每次选择结束时间最早的活动。具体实现中,我们将活动按照结束时间从小到大排序,然后依次选择活动,如果当前活动的开始时间大于等于前一个活动的结束时间,则可以选择该活动。最后返回选择的活动数即可。 时间复杂度为 O(n log n),其中 n 是活动数。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值