12月3号c语言解题笔记--贪心算法篇

题①----------------------------逛画展--------------------------------

        博览馆正在展出由世上最佳的m位画家所画的图画,游客在购买门票时必须说明两个数字,a和b,代表他要看展览中的第a幅至第b幅画(包含a, b)之间的所有图画,而门票的价钱就是一张图画一元。

        Sept希望入场后可以看到所有名师的图画。当然,他想最小化购买门票的价格。请求出他购买门票时应选择的a,b,数据保证一定有解。若存在多组解,输出a最小的那组。

输入格式

第一行两个整数n,m,分别表示博览馆内的图画总数及这些图画是由多少位名师的画所绘画的。

第二行包含n个整数a,代表画第幅画的名师的编号。

输出格式

一行两个整数a, b。


先分析题目的要求:

  1. n为总画数,m为画家数,需要按一定顺序,看完所有名师的图画。
  2. 找出价格最少,即需看完全部画家后,看到画最少。
  3. 若存在多组解,输出a最小的那组。

        这样每次循环(记为循环画定位画家)下去,每次画家都+1;直到画家的数组people都不为0了之后,为一个循环。

整体思路如下:找出那组解使全部都看完画家的画----比较多组解,找最小

先定义变量

int picture[1000000];
int people[2005];
int n, m;      //n为画,m为画家
int min = n;  //假定最小价格是n,即全部看完
int a,b;      //a为左区间,b为右区间
int num = 0;  //用来计数

但问题来了,怎么实现画家的数组people都不为0呢,难道需要在最后循环遍历每个元素,

判断people[i]==0吗,这也许太复杂了,我们有以下想法:

  • 我们需在开始循环(循环画定位画家)时,增加一个判断,定义变量为num,如果people数组为0,num++;直到num=画家数,就可以知道“实现画家的数组people都不为0”

部分代码如下:

        那么,怎么找出最小的那组解呢,我们采用做最大值假设法(贪心算法假设最初的就是最优解),原理是:

  •      我们开始假定最大值  (min)   是我们的最小那组解,每次得到的解结束后,都与这个值比较,若得到的解小于初始的值(min),则用之后得到的数据解覆盖之前的数据
int min = n;//假定最小价格是n,即全部看完
if (num == m && j-i<min)
{
    min = j - i;  //覆盖初始数值
    a = i;
    b = j;
}

最后代码如下

#include<stdio.h>
int picture[1000000];
int people[2005];
int main()
{
	int n, m;//n为画,m为画家
	scanf("%d %d", &n, &m);
	for (int i = 1; i <=n; i++)
	{
		scanf("%d", &picture[i]);
	}
	int min = n;//假定最小价格是n,即全部看完
	int a,b;//a为左区间,b为右区间
	for(int i=1;i<=n-m;i++)
	{
        int num = 0;
        for (int i = 0; i <= m; i++)//每次都初始化,都设为0
	    {
            people[i]=0;
	    }
		for (int j = i; j <=n; j++)
		{
		    if (people[picture[j]] == 0){num++; }
			people[picture[j]]++;  //记录看过的画是哪位画家的次数
			
			if (num == m && j-i<min)
			{
                min = j - i;
			    a = i;
			    b = j;
                break;
            }
            else if(num==m)//不是最小解,即不做处理,退出循环
                break;
		}
        
	}
	printf("%d %d\n",a,b);
    return 0;
}

最后,声明几个易错点:

  • 数组需定义为全局变量,如果定义为局部变量,数组未初始化,数组元素为随机值,影响后面判断。


  • 题后反思,将代码提交,发现有一组数据超时了

原来代码在for循环又嵌套了两个循环,时间复杂度比较高,考虑改进算法

上是前面算法,每一个画的编号都要循环一次,复杂度高

我们可以采用左右区间移动法:

  1. 从编号1开始左区间不动(a不动),右区间向右移动(b动)
  2. 直到有解(全部画家都有看过num=m),这时还不是最小解。
  3. 这时a移动,移动过程中,如果经过的画家的次数大于一次,则可移动,同时这个画家次数减一个单位,如果经过的画家的次数小于一次,则a不能经过这个画家。
  4. 若a不能经过这个画家,b再移动,以此类推

代码奉上

#include<stdio.h>
int n,m;//n为总画数
int picture[1000005];
int people[2005];
int num=0,left=1;
int main()
{
    
    scanf("%d %d",&n,&m);
    int min=n,a=0,b=0;
    for(int i=1;i<=n;i++){scanf("%d",&picture[i]);}
    for(int i=1;i<=n;i++)
    {
        if(people[picture[i]]==0){num++;}
        people[picture[i]]++;
        if(num==m)
        {
            while(people[picture[left]]>1)//左右区间移动
            {
                people[picture[left]]--;
                left++;
            }
            if(i-left<min)
            {
                min=i-left;
                a=left;
                b=i;
                
            }
        }
    }
    printf("%d %d\n",a,b);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值