关于贪心算法

        ~~~第一次写博客,写得不好请见谅~~~

        这是一个非常基础的算法,最适合我这种因为期末考几个星期没有碰代码的人了。为什么写贪心?原因如下:1.老师让我们坚持写博客,所以打算从易到难,先写容易的算法;2.最近做题多数(三分之一)都是贪心;3.来源于一个故事:

        

         (那是一个刚成为oier不久的晚上……)

          A同学:(指着我刚AC的一个贪心题)这题怎么做?

          我:(不假思索)贪心。

          A同学:贪心是什么?我一直没有弄清楚,和dp有什么区别吗?

        (我陷入了沉思……是啊,什么是贪心呢?)

       

        用我的话理解,就是局部最优解,即是整体最优解吧!也就是说你并不需要考虑全局,只用考虑此刻!

        这岂不是美哉?所以说,贪心一般时间复杂度低,码量也少。

        但有的人说了,怎么区分是否是贪心算法。我只能说对于简单的贪心,先猜,在稍加在脑海中证明。

        来几道最简单的例题:luogu合并果子 , (刚做的)CF Pair Programming

        首先是合并果子,这是很早以前做的一个题了……

        简单概括题意:一共有n堆果子,每堆果子ai个,合并ai堆和aj堆所需要的代价是ai+aj,现在将这堆果子合并成一堆的最小代价是?

        

        不难得出,一共要合并n-1次,在这n-1次里,为了让我们最后的代价最小,目前当然要合并最少的两堆。那我们合并完了就不管了吗?当然不是,我们还要把它塞回到队列中去。于是,这题我便用了堆来维护队列的最小值。

代码如下:

#include<bits/stdc++.h>
using namespace std;
int a[11000],al=0;//a是小根堆,al是它的长
int n;
void ins(int w)
{
	a[++al]=w;
	int now=al;
	while(now!=1)
	{
		if(a[now/2]>a[now])swap(a[now/2],a[now]);
		else break;
		now/=2;
	}
}//插入
int pop()
{
	swap(a[1],a[al]);
	int now=1;
	while(now*2<al)
	{
		int c=now*2;
		if(a[c]>a[c+1]&&c+1<al)c++;
		if(a[c]<a[now])swap(a[c],a[now]);
		else break;
		now=c;
	}
	al--;
	return a[al+1];
}//弹出,当时还有手打堆得习惯
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		ins(x);//把每一堆果子丢进a数组中
	}
	int ans=0;
	for(int i=1;i<n;i++)
	{
		int x=pop();
		int y=pop();//弹出两个最小值
		x+=y;//将它们相加
		ans+=x;//记录一下代价
		ins(x);//把新的一堆塞回到数组中去
	}
	printf("%d",ans);
	return 0;
}

之后是CF的一道(大概无翻译)

题意大概是:有两个人共享一份文件,最开始有k行。A进行了n个操作[a1,a2,a3……,an],B进行了m个操作[b1,b2,b3……,bn]。A和B要严格按照从左到右的顺序操作,每次可以A和B任意一个人操作。操作0表示我要写一行新的,操作i(0<i<=n+m)表示修改之前的第i行。当然,如果修改的i行大于现有的行数,是不存在的。请输出其中一种合法的步骤。

规定从前往后的顺序,则可以得出一个尽可能多的增加行数的结论。当然,当前合法的我们也可以操作。最后尽可能把不合法的留到最后,因为到那时总行数绝对是最多的(总行数不会减小只会增加)

因此代码如下:

#include<bits/stdc++.h>
using namespace std;
int T,n,m,k,a[11000],b[11000],ans[11000],t;
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d%d",&k,&n,&m);
		for(int i=1;i<=n;i++)scanf("%d",&a[i]);
		for(int i=1;i<=m;i++)scanf("%d",&b[i]);
		int l=1,r=1;t=0;//l为当前枚举到a数组的下标,r为b数组下标,从前往后枚举
		bool p=0;//用来判断合不合法的
		for(int i=1;i<=n+m;i++)
		{
			if((a[l]<b[r]||r>m)&&l<=n)
			{
				if(a[l]>k){p=1;break;}//无法修改比当前总行数更大的行
				else{ans[++t]=a[l];l++;}
				if(a[l-1]==0)k++;//上一步l已经加过一个1了,所以是“l-1”
			}
			else
			{
				if(b[r]>k){p=1;break;}
				else{ans[++t]=b[r];r++;}
				if(b[r-1]==0)k++;
			}
		}
		if(p)printf("-1");//不合法输出-1
		else for(int i=1;i<=t;i++)printf("%d ",ans[i]);
		printf("\n");
	}
	return 0;
}

以后可能大概放假期间会每周一更,所以打算以一个简单一点的开个头(否则后面都不知道该怎么写)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值