总之就是 | 初见 | 基础算法 | 贪心 +

前言

看过我之前的咕List的应该知道,实际上这个坑最一开始是要整理贪心的题目的,但是从二月拖到四月,一直就没填上这个坑,再加上好久没做贪心的题目,于是乎就简单做了做一本通TG上的题目

但是这些题太少了,笔记部分也比较多,也不能纯算是题解系

所以我就缝合了标题

但是叙述顺序将以笔记为主,题为辅

贪心

实际上本来不想写什么是贪心的,但是发现自己之前没有写过,于是乎简单写一下

贪心,可以直接从字面来理解,就是每次选择当前最优情况,不管后面的好坏

也就是说我们每次做出的只是局部的最优解,而非一定是全局最优解,但是有时是可以根据纯贪心得到最优解的,也就是在全局最优解包含局部最优解的时候

常见贪心题目类别

简单类

指难度[普及-]及以下

最优装载问题 And 部分背包问题

描述

对于前者:

\(n\)个物体,第\(i\)个物品的费用为\(w_i\),求总费用不超过\(c_{max}\)时的最大能装载的物品数量

后者只比前者多了一点:

\(n\)个物体,第\(i\)个物品的费用为\(w_i\),价值为\(v_i\),求总费用不超过\(c_{max}\)时的最大能装载的物品价值\(v_{max}\)每个物品可以只拿一部分,且价值是均匀分布的

Solution

前者直接按照\(w\)升序排序,若\(sum+w_i≤c_{max}\)一直选当前物品,直到不符合条件为止,在过程中一直统计放进去的个数即可

至于后者,它和DP背包的最大的不同就是每个物品可以只拿一部分,且价值是均匀分布的

这句话也就给了我们贪的空间:我们可以先算出每个物体的平均价值\(p_i\),然后根据\(p_i\)降序排序,若\(sum+=w_i≤c_{max}\),我们选整个的物品,若不符合上面那个条件了,就选直到\(c_{max}-sum\)大小的部分物品

乘船问题

描述

\(n\)个元素,都有各自的权值\(w_i\)分为每段不超过\(x\)的线段,求最小的线段数

乘船版本描述

\(n\)个人,每个人都有其各自的重量\(w_i\),每条船的最大载重量是\(x\),求最少要几条船

Solution

先升序或降序排列(让所有\(w\)排列起来具有单调性即可),然后从两头选(每次选权值最大的和权值最小的)

较简单类

指难度为[普及-][普及/提高-]的叠加态[普(通)高(中)-/CSP-J(可能)提及-/普及=](雾)

限期罚款问题

描述

\(n\)个任务,每个都需要一个单位时间完成,但是每个任务在其截至时间前未完成就要进行对应罚款\(w_i\)

Solution

先完成\(w_i\)大的任务,若无法在要求的时间内完成,那么把他放在最后,反正都是未完成的惩罚一样的

下面有一个简单的例题P1230 智利大冲浪([普及/提高-],这是一道非常典型的这种贪心策略

Code
#include <bits/stdc++.h>
#define Heriko return
#define Deltana 0
#define S signed
#define LL long long
#define R register
#define I inline
#define D double
#define LD long double
using namespace std;
I void fr(LL &x)
{
	LL f=1;char c=getchar();
	x=0;
	while(c<'0'||c>'9')
	{
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9')
	{
		x=(x<<3)+(x<<1)+c-'0';
		c=getchar();
	}
	x*=f;
}
LL tot,n,ans;
bool t[501];
struct game
{
	LL a,b;
}f[501];
I bool cmp(R game x,R game y)
{
	Heriko x.b>y.b;
}
I void into()
{
	fr(tot),fr(n);
	memset(t,false,sizeof(t));
	for(R LL i=1;i<=n;i++) fr(f[i].a);
	for(R LL i=1;i<=n;i++) fr(f[i].b);
	sort(f+1,f+1+n,cmp);
}
I void todo()
{
	for(R LL i=1;i<=n;i++)
	{
		bool can=false;
		for(R LL j=f[i].a;j>=1;j--) if(!t[j]) {can=t[j]=true;break;}
		if(!can)
		{
			for(R LL j=n;j>=1;j--) if(!t[j]) {t[j]=true;break;}
			ans+=f[i].b;
		}
	}
	std::cout<<tot-ans<<endl;
}
S main()
{
	std::ios::sync_with_stdio(false);
	into();
	todo();
	Heriko Deltana;
}

普通类

指难度为[普及/提高-]左右

选择不相交区间问题

描述

\(n\)个开区间\((a_i,b_i)\)中选取尽可能多的区间,使得两个区间无公共点

Solution

按照b(也就是区间结束点)进行升序排序,再依次考虑:若不和之前的区间重合就选这个区间

区间选点问题

描述

给定\(n\)个闭区间\([a_i,b_i]\),再数轴上选点,使得每个区间至少有\(k\)个点,求最少选几个点

Solution

因为求最少选几个点才能达到目的,因此我们的贪心策略就是让每个点的利用率最大化

考虑到我们已知的区间可能会有重叠的部分,那么在这个区域放\(k\)个点可以直接满足所有包含这个重叠的区间,这很明显是要比在重叠部分外的地方放点要优

下面给出一道例题P1250种树([普及/提高-]

思路和上面所说的基本相同,只不过在这\(k=1\)而已

Code
#include <bits/stdc++.h>
#define Heriko return
#define Deltana 0
#define S signed
#define LL long long
#define R register
#define I inline
#define D double
#define LD long double
using namespace std;
I void fr(int &x)
{
	LL f=1;char c=getchar();
	x=0;
	while(c<'0'||c>'9')
	{
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9')
	{
		x=(x<<3)+(x<<1)+c-'0';
		c=getchar();
	}
	x*=f;
}
struct area
{
	int a,b,c;
}f[5015];
int n,m;
bool vis[114514];
I int cmp(area x,area y)
{
	Heriko x.b<y.b;
}
I void into()
{
	memset(vis,false,sizeof(vis));
	fr(n),fr(m);
	for(R int i=1;i<=m;i++) fr(f[i].a),fr(f[i].b),fr(f[i].c);
	sort(f+1,f+1+m,cmp);
	
}
I void todo()
{
	R int ans=0;
	for(R int i=1;i<=m;i++)
	{
		int k=0;
		for(R int j=f[i].a;j<=f[i].b;j++) if(vis[j]) k++;
		if(k>=f[i].c) continue;
		for(R int j=f[i].b;j>=f[i].a;j--)
		{
			if(!vis[j])
			{
				vis[j]=true;
				k++;
				ans++;
				if(k==f[i].c) break;
			}
		}
	}
	std::cout<<ans;
}
S main()
{
	std::ios::sync_with_stdio(false);
	into();
	todo();
	Heriko Deltana;
}

区间覆盖问题

描述

给定\(n\)个闭区间,选择尽量少的区间,使得这些区间能够覆盖[s,t]

Solution

按照左端点进行升序排序,依次寻找能覆盖指定区间的最大的区间,然后把要覆盖的区间的左端点变成当前选择的区间的右端点,直到全部覆盖最初所定的[s,t]

流水作业问题

描述

\(n\)个作业要在两台机器\(M_1,M_2\)上面加工,每个物品都必须现在\(M_1\)上用\(a_i\)的时间加工,再从\(M_2\)上用\(b_i\)的时间加工,求加工完\(n\)的最短时间

Solution

最优调度一定让\(M_1\)没有空闲,让\(M_2\)的空闲时间尽可能的少

下面利用一个例题来讲一讲,P1248 加工生产调度([提高+/省选-]

思路

上来一看题目,这就已经提示的很明显了,流水作业问题

再一看题目描述,就是裸的流水作业问题,于是借助此题就来简单说一下这个流水作业问题的贪心策略

下面来举一个比较通俗易懂的例子来帮助理解贪心策略:

\((a_1,a_2,a_3,a_4,a_5)=(1,2,3,5,7)\)

\((b_1,b_2,b_3,b_4,b_5)=(5,1,6,2,3)\)

我们设\(m_i=min(a_i,b_i)\),那么对于这个栗子有:

\((m_1,m_2,m_3,m_4,m_5)=(1,1,3,2,3)\)

然后对所有的\(m\)进行升序排序:\((m_1,m_2,m_4,m_3,m_5)=(1,1,2,3,3)\)

然后我们进行如下处理:因为我们要保证\(M_1\)没有空闲,\(M_2\)尽可能多的利用起来,所以我们可以根据刚才得出的顺序来进行如下排列,\(a_i<b_i\)的作业,也就是\(m_i=a_i\)的作业从队首开始安排,反之则从队尾开始安排

那么我们就能得到如下的排列过程:

\(∵m_1=a_1∴(1,,,)\)

\(∵m_2=b_2∴(1,,,,2)\)

\(∵m_3=a_3∴(1,3,,,2)\)

\(∵m_4=b_4∴(1,3,,4,2)\)

\(∵m_5=b_5∴(1,3,4,5,2)\)

那么我们最后得到的最优作业排列就是\(1,3,4,5,2\)

Code
#include <bits/stdc++.h>
#define Heriko return
#define Deltana 0
#define S signed
#define LL long long
#define R register
#define I inline
#define D double
#define LD long double
using namespace std;
I void fr(LL &x)
{
	LL f=1;char c=getchar();
	x=0;
	while(c<'0'||c>'9')
	{
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>='0'&&c<='9')
	{
		x=(x<<3)+(x<<1)+c-'0';
		c=getchar();
	}
	x*=f;
}
struct work
{
	LL a,b,s,m;
}f[1015];
LL n,ans[1015];
I void into()
{
	fr(n);
	for(R LL i=1;i<=n;i++) fr(f[i].a);
	for(R LL i=1;i<=n;i++) fr(f[i].b);
}
I void todo()
{
	for(R LL i=1;i<=n;i++) f[i].m=min(f[i].a,f[i].b),f[i].s=i;
	for(R LL i=1;i<n;i++) for(R LL j=i+1;j<=n;j++) if(f[i].m>f[j].m) swap(f[i].m,f[j].m),swap(f[i].s,f[j].s);
	//for(R LL i=1;i<=n;i++) std::cout<<i<<": "<<f[i].a<<' '<<f[i].b<<' '<<f[i].m<<' '<<f[i].s<<endl;
	R int k=0,temp=n+1;
	for(R LL i=1;i<=n;i++)
	{
		if(f[i].m==f[f[i].s].a) ans[++k]=f[i].s;
		else ans[--temp]=f[i].s;
	} 
	k=temp=0;
	for(R LL i=1;i<=n;i++)
	{
		k+=f[ans[i]].a;
		if(temp<k) temp=k;
		temp+=f[ans[i]].b;
	}
	std::cout<<temp<<endl;
	for(R LL i=1;i<=n;i++) std::cout<<ans[i]<<' ';
	std::cout<<endl;
}
S main()
{
	std::ios::sync_with_stdio(false);
	into();
	todo();
	Heriko Deltana;
}

End

总的来说呢,是填上了一个老坑吧

posted @ 2021-04-10 20:47  HerikoDeltana  阅读(93)  评论(0编辑  收藏  举报