总之就是 | 初见 | 基础算法 | 贪心 +
前言
看过我之前的咕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
总的来说呢,是填上了一个老坑吧