2024dsfz集训Day1:贪心算法
DAY1:贪心算法
\]
特别感谢 此次课的主讲 - Kwling
经典模型:
硬币问题:
找零钱问题:
有 \(100\) 元、\(50\) 元、\(20\) 元、\(10\) 元、\(5\) 元和 \(1\) 元这些面值的钱- 求凑出 \(\) 元的最多张数和最少张数,要求第 \(\) 种面值的钱不能用超过 \(_\) 张
- 多组询问,\( ≤ 10^5,_ ≤ 10^9, ≤ 10^9\) 。
解题思路:
- 首先,我们需要将货币面值存储在一个数组中,按照从大到小的顺序排列,即 \(\{100, 50, 20, 10, 5, 1\}\) 。
- 对于求最多张数的情况:
- 从最小面值开始,尽可能多地使用该面值的货币,直到达到使用该面值的上限 \(a_i\) 或凑够所需金额 \(x\) 。
- 重复此过程,逐步使用更大面值的货币,直到凑够 \(x\) 元或无法再凑出。
- 对于求最少张数的情况:
- 从最大面值开始,尽可能多地使用该面值的货币,直到达到使用该面值的上限 \(a_i\) 或凑够所需金额 \(x\) 。
- 重复此过程,逐步使用更小面值的货币,直到凑够 \(x\) 元或无法再凑出。
部分背包
例如:洛谷P2240 |【深基12.例1】部分背包问题
解法: 对于所有物品,按性价比排序再按一定顺序计算可得结果。区间问题
- 选择若干不相交区间问题
例题:
思路: 以结束时间为第一关键字排序,依次考虑各个活动,如果没有和已经选择的活动冲突,就选,否则就不选。
- 区间覆盖或线段覆盖问题
例题:
思路: 最左边的线段放什么最好?
显然放右端点最靠左的线段最好,从左向右放,右端点越小妨碍越少。
其他线段放置按右端点排序,贪心放置线段,即能放就放。(类似若干不相交区间问题)
- 区间分段问题
例题:
思路: 这个应该算是最简单最基础的贪心了,甚至都不用排序,从前往后扫,一旦和大于 \(m\) ,再分新的一段。
- 选择若干不相交区间问题
顺序问题
Accoders P2037 |【一本通提高篇贪心】 数列极差
解法: 可以发现,按照小数到大数的顺序跑题目里的公式可以得到最大值,按照大数到小数的顺序可以得到最小值。
那我们直接用堆来维护顺序,一个大根堆,一个小根堆,每次弹堆顶的两个数参与运算,运算完毕后再 \(push\) 进堆即可。
一般解题步骤
- 建立数学模型来描述问题(把问题简化成学过的知识,术语,或者算法);
- 把求解的问题分成若干个子问题(大事化小);
- 对每一子问题求解,得到子问题的局部最优解(小事化了);
- 把子问题的局部最优解合成原来问题的一个解(合并所有答案得到最终答案)。
范围及证明
决定一个贪心算法是否能找到全局最优解的条件:
\({\color{Blue}\large \mathcal{有最优子结构}}\):很好理解,就是指一个问题的最优解包含其子结构的最优解,和动态规划上理解是一样的。
\({\color{Blue}\large \mathcal{有最优贪心选择属性}}\):而贪心选择性是指所求问题的整体最优解可以通过一系列可以通过一系列局部最优的选择来达到。他总是作出当前最好的选择,该选择可以依赖于之前的选择,但绝不依赖于将来的选择和子问题的选择,这是他与动态规划的重要区别。
一般我们证明一个题目可以用贪心就是证明上面两点均满足。
反证法: 如果交换方案中任意两个元素/相邻的两个元素后,答案不会变得更好,那么可以推定目前的解已经是最优解了。
归纳法: 先算得出边界情况(例如 \(n=1\) )的最优解 \(F_1\) ,然后再证明:对于每个 \(n\) ,\(F_{n+1}\) 都可以由 \(F_n\) 推导出结果。
归纳法例题:
题目解法&思路
\(T1\)
- AccodersP2032 |【一本通提高篇贪心】活动安排
思路: 我们按照活动的结束时间进行升序排序,因为一个活动结束的越早,就越不容易与其他活动起冲突,从前往后枚举时判断一下就行了
#include <bits/stdc++.h>
using namespace std;
struct node{
int x,y;
}a[101010];
int n;
bool cmp(node xx,node yy){
return xx.y < yy.y;
}
int main(Designer = 洛谷@Lwj54joy,uid=845400){
cin>>n;
for(int i = 1;i <= n;i++){
cin>>a[i].x>>a[i].y;
}
sort(a+1,a+1+n,cmp);
int ans = 1,ls;
ls = a[1].y;
for(int i = 2;i <= n;i++){
if(a[i].x >= ls) ans++,ls = a[i].y;
}
cout<<ans<<endl;
}
\(T2\)
- AccodersP2033 |【一本通提高篇贪心】种树
- LuoguP9836 | 种树
思路 :依然是按照路段的结束位置进行升序排序,以便于我们从前往后按顺序枚举,枚举每一个路段时,从路段的开头枚举到结尾,由于一个位置只能种一棵树,所以只要这个位置有树,这个路段要求的树的数量就-1,具体看代码
#include <bits/stdc++.h>
using namespace std;
bool f[101010];
struct node{
int x,y,z;
}a[101010];
int n;
bool cmp(node xx,node yy){
return xx.y < yy.y;
}
int h;
int main(Designer = 洛谷@Lwj54joy,uid=845400){
cin>>n>>h;
for(int i = 1;i <= h;i++){
cin>>a[i].x>>a[i].y>>a[i].z;
}
sort(a+1,a+1+h,cmp);
int ans = 0;
for(int i = 1;i <= h;i++){
int lim = a[i].z;
for(int j = a[i].x;j <= a[i].y and lim;j++){
if(f[j]) lim--;
}
if(lim != 0){
for(int j = a[i].y;j >= a[i].x and lim;j--){
if(f[j]) continue;
ans++;
f[j] = true;
lim--;
}
}
}
cout<<ans<<endl;
}
\(T3\)
- AccodersP2034 |【一本通提高篇贪心】喷水装置
思路 :因为喷洒范围是圆,所以我们要利用勾股定理来求出这个有效范围 贪心忽然成了数学题,我们可以求出一个喷头有效范围的最左端和最右端,按照最左端进行升序排序,以便不会漏下没覆盖到的区域,从左到右一个一个检查,还要考虑能不能喷到上下边缘。具体看代码
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
struct node {
double l, r;
bool operator<(const node& W) const {
return l < W.l;
}
};
int main(Designer = 洛谷@Lwj54joy,uid=845400){
int T;
std::cin >> T;
while (T--) {
int cnt = 0;
int n;
double l, w;
std::cin >> n >> l >> w;
std::vector<node> a;
double w2 = w / 2.0;
for (int i = 0; i < n; ++i) {
double x, y;
std::cin >> x >> y;
if (y <= w / 2) {
continue;
}
node temp;
temp.l = x - std::sqrt(y * y - w * w / 4);
temp.r = x + std::sqrt(y * y - w * w / 4);
a.push_back(temp);
}
std::sort(a.begin(), a.end());
double s = 0, e = l;
int i = 0, ans = 0;
bool t = false;
while (s < e) {
double vis = -2e9;
size_t j;
for (j = i; j < a.size(); ++j) {
if (a[j].l <= s) {
vis = std::max(vis, a[j].r);
} else {
break;
}
}
if (s < e && vis < s) {
std::cout << -1 << "\n";
t = true;
break;
}
ans++;
s = vis;
i = j;
}
if (!t) std::cout << ans << "\n";
}
}
\(T4\)
- AccodersP2035 |【一本通提高篇贪心】加工生产调度
- 洛谷P1248 | 加工生产调度
思路 :\(\mathbf{{\color{Red} {\scriptsize 【请先确保您已经对题面足够熟悉再阅读思路】} } }\) 第一个产品在 \(A\) 加工时,没有其他产品在 \(B\) 加工,所要选 \(a\) 小的充当最后一个物品,在 \(B\) 加工时,没有其他产品在 \(A\) 加工,以要选 \(b\) 小的,对于其他产品,如果该产品的 \(a\) 比 \(b\) 小,那么把这产品放到前面去,它越早离开 \(A\) 工厂,其他产品就能越早进入 \(A\) 工厂;如 \(b\) 比 \(a\) 小,那就往后放,它越早离开 \(B\) 工厂,其他产品就能越早入 \(B\) 工厂。为什么这么排呢?因为一个物品必须先进入 \(A\) 工厂,在进 \(B\) 工厂,我们先尽快让所有产品进入 \(A\) 工厂,再尽快让所有产品离 \(B\) 工厂,这就是我们的贪心策略。
#include <bits/stdc++.h>
using namespace std;
struct node{
int w,id;
}e[1010];
int a[1010],b[1010],n;
bool cmp(node xx,node yy){
return xx.w < yy.w;
}
int main(Designer = 洛谷@Lwj54joy,uid=845400){
cin>>n;
for(int i = 1;i <= n;i++) cin>>a[i];
for(int i = 1;i <= n;i++) cin>>b[i];
for(int i = 1;i <= n;i++) e[i] = {min(a[i],b[i]),i};
sort(e+1,e+1+n,cmp);
int q[1010] = {0},l = 0,r = n+1;
for(int i = 1;i <= n;i++)
if(e[i].w == a[e[i].id]) q[++l] = e[i].id;
else q[--r] = e[i].id;
int ta = 0,tb = 0;
for(int i = 1;i <= n;i++){
ta += a[q[i]];
tb = max(ta,tb);
tb += b[q[i]];
}
cout<<tb<<endl;
}
\(T5\)
- AccodersP2036 |【一本通提高篇贪心】智力大冲浪
- 洛谷P1230 | 智力大冲浪
思路 :我们按照每个游戏所扣除的钱降序排序,因为所扣除的钱越大,对答案的影响就越大,我们让每个游戏都在Deadline前一个时间单位做,那它之前的时间就可以分配给其他游戏了,如果它找到可以占用的时间点,那就在数组中标记一下此时间点已被占用,如果它找不到可以占用的时间,那就只能扣钱了。
#include <bits/stdc++.h>
using namespace std;
struct node{
int t,w;
bool operator<(const node &W) const{
return w>W.w;
}
}a[1010];
bool st[1010];
int n,m;
int main(Designer = 洛谷@Lwj54joy,uid=845400){
cin>>m>>n;
for(int i = 1;i <= n;i++) cin>>a[i].t;
for(int i = 1;i <= n;i++) cin>>a[i].w;
sort(a+1,a+1+n);
for(int i = 1;i <= n;i++){
bool flag = false;
for(int k = a[i].t;k >= 1;k--){
if(!st[k]){
flag = true;
st[k] = 1;
break;
}
}
if(!flag) m -= a[i].w;
}
cout<<m<<endl;
}
\(T6\)
- AccodersP2037 | 【一本通提高篇贪心】数列极差
思路 :可以发现,按照小数到大数的顺序跑题目里的公式可以得到最大值,按照大数到小数的顺序可以得到最小值。
那我们直接用堆来维护顺序,一个大根堆,一个小根堆,每次弹堆顶的两个数参与运算,运算完毕后再 \(push\) 进堆即可。
#include <bits/stdc++.h>
using namespace std;
int a[1110100];
int n;
struct cmp1{
bool operator()(int x,int y){
return x < y;
}
};
struct cmp2{
bool operator()(int x,int y){
return x > y;
}
};
int main(Designer = 洛谷@Lwj54joy,uid=845400){
priority_queue<int,vector<int>,cmp1> q1; //大根堆
priority_queue<int,vector<int>,cmp2> q2; //小根堆
cin>>n;
for(int i = 1;i <= n;i++){
cin>>a[i];
q1.push(a[i]);
q2.push(a[i]);
}
while(q1.size() >= 2){
int xx = q1.top();
q1.pop();
int yy = q1.top();
q1.pop();
q1.push(yy*xx+1);
}
while(q2.size() >= 2){
int xx = q2.top();
q2.pop();
int yy = q2.top();
q2.pop();
q2.push(yy*xx+1);
}
cout<<q2.top() - q1.top()<<endl;
}
\(T7\)
- AccodersP2038 |【一本通提高篇贪心】数列分段
思路 :这个应该算是最简单最基础的贪心了,甚至都不用排序,从前往后扫,一旦和大于 \(m\) ,再分新的一段
#include <bits/stdc++.h>
using namespace std;
int a[101010];
int n,m;
int main(Designer = 洛谷@Lwj54joy,uid=845400){
cin>>n>>m;
for(int i = 1;i <= n;i++){
cin>>a[i];
}
int ans = 1,kkk = 0;
for(int i = 1;i <= n;i++){
if(kkk+a[i] <= m) kkk += a[i];
else kkk = a[i],ans++;
}
cout<<ans<<endl;
}
\(T8\)
- AccodersP2039 |【一本通提高篇贪心】线段
- 洛谷P1803 | 凌乱的yyy / 线段覆盖
思路: 跟 \(T1\) 除了题面不同外,基本一模一样,按照每条线段的右端点升序排序,扫一遍即可
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
using namespace std;
struct node{
int x,y;
}a[1010010];
int n;
bool cmp(node xx,node yy){
return xx.y < yy.y;
}
int main(Designer = 洛谷@Lwj54joy,uid=845400){
cin>>n;
for(int i = 1;i <= n;i++){
scanf("%d%d",&a[i].x,&a[i].y );
}
sort(a+1,a+1+n,cmp);
int ans = 1,ls;
ls = a[1].y;
for(int i = 2;i <= n;i++){
if(a[i].x >= ls) ans++,ls = a[i].y;
}
cout<<ans;
}
\(T9\)
- AccodersP2041 |【一本通提高篇贪心】钓鱼
思路: 该题的难点主要在最后可在任意湖边停住,而且不能往回走,在一个湖钓鱼时的效率还会越来越少。
常规的思路看来是不行的了,题目好多动态未知的量,唯有我们更换角度,“化动为静”:即然最后不知道停在哪个湖,那就分类讨论呗。
把停在每个湖的最优解全部求出,在最后取个最优解不就行了吗?
发现当我们知道主人公最后停在哪个湖后,她的路径也就唯一确定了(例如佳佳最后停在了第i个湖,那么她的路径一定是 \(1->2->3->......->i\) ),同时她的纯钓鱼时间可由总空闲时间减去行程时间唯一确定。
考虑从哪个湖钓鱼一个 \(5\) 分钟,就相当于在路径 \(1->2->3->......->i\) 中的一个节点上“堆”上一个标记表示在这个湖又钓了 \(5\) 分钟的鱼,显然这里可用贪心策略,每次标记目前为止五分钟钓鱼数目最大的那个湖,并使当前记录答案的 \(sumi+=\) 在那个湖又钓的鱼数。最后比较所有的 \(sumi(i=1,2,...,n)\) 取最大的输出就行了。
#include <bits/stdc++.h>
using namespace std;
int n,leisure,fish[200],trip[1010],delta[1010];
struct node{
int id,key;
bool operator < (const node &rhs)const{
return key<rhs.key;
}
};
int main(Designer = 洛谷@Lwj54joy,uid=845400){
cin>>n>>leisure;
leisure *= 12;
node t;
int answer = -1;
for(int i = 1;i <= n;i++){
cin>>fish[i];
}
for(int i = 1;i <= n;i++){
cin>>delta[i];
}
for(int i = 1;i < n;i++){
cin>>trip[i];
trip[i] += trip[i-1];
}
int lt,ans;
for(int i = 1;i <= n;i++){
lt = leisure-trip[i-1],ans = 0;
priority_queue<node> q;
for(int j = 1;j <= i;j++) q.push({j,fish[j]});
for(int j = 1;j <= lt;j++){
t=q.top();
q.pop();
if(t.key < 0) break;
ans += t.key;
q.push({t.id,t.key-delta[t.id]});
}
answer = max(answer,ans);
}
cout<<answer<<endl;
}
\(T1\)
- AccodersP2042 |【一本通提高篇贪心】糖果传递
思路: 先以第1号小朋友为起点,算出每个小朋友需要向他后边的小朋友传递的糖果数量。
考虑如果以第i+1号小朋友为起点,则传递数量为其他传递数量与第i号小朋友向后传递数量的差值。
那么选传递数量的中位数,就可以得到最小传递数量。
#include <bits/stdc++.h>
using namespace std;
int a[1010101];
long long c[1010101];
long long sum,n;
int main(Designer = 洛谷@Lwj54joy,uid=845400){
cin>>n;
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
sum += a[i];
}
long long ave = sum/n;
for(int i = n;i >= 1;i--) c[i] = c[i+1]+ave-a[i];
c[1] = 0;
sort(c+1,c+1+n);
long long ans = 0;
for(int i = 1;i <= n;i++){
ans += abs(c[i]-c[(n+1)/2]);
}
cout<<ans<<endl;
}
\(\huge Thanks.\)
感谢阅读,若有问题或错误,欢迎评论(或私信我)。
2025 Designed By @洛谷Lwj54joy,uid=845400
2024dsfz集训Day1:贪心算法的更多相关文章
- LOJ 6060「2017 山东一轮集训 Day1 / SDWC2018 Day1」Set(线性基,贪心)
LOJ 6060「2017 山东一轮集训 Day1 / SDWC2018 Day1」Set $ solution: $ 这一题的重点在于优先级问题,我们应该先保证总和最大,然后再保证某一个最小.于是我 ...
- 【欧拉回路+最小生成树】SD开车@山东2018省队一轮集训day1
目录 [欧拉回路+最小生成树]SD开车@山东2018省队一轮集训day1 PROBLEM 题目描述 输入 输出 样例输入 样例输出 提示 SOLUTION CODE [欧拉回路+最小生成树]SD开车@ ...
- 牛客2018国庆集训 DAY1 D Love Live!(01字典树+启发式合并)
牛客2018国庆集训 DAY1 D Love Live!(01字典树+启发式合并) 题意:给你一颗树,要求找出简单路径上最大权值为1~n每个边权对应的最大异或和 题解: 根据异或的性质我们可以得到 \ ...
- 贪心算法(Greedy Algorithm)
参考: 五大常用算法之三:贪心算法 算法系列:贪心算法 贪心算法详解 从零开始学贪心算法 一.基本概念: 所谓贪心算法是指,在对问题求解时,总是做出在当前看来是最好的选择.也就是说,不从整体最优上加以 ...
- 算法导论----贪心算法,删除k个数,使剩下的数字最小
先贴问题: 1个n位正整数a,删去其中的k位,得到一个新的正整数b,设计一个贪心算法,对给定的a和k得到最小的b: 一.我的想法:先看例子:a=5476579228:去掉4位,则位数n=10,k=4, ...
- LEETCODE —— Best Time to Buy and Sell Stock II [贪心算法]
Best Time to Buy and Sell Stock II Say you have an array for which the ith element is the price of a ...
- ACM_ICPC hdu-2111(简单贪心算法)
一道非常简单的贪心算法,但是要注意输入的价值是单位体积的价值,并不是这个物品的总价值!#include <iostream> #include <stdio.h> #inclu ...
- 基于贪心算法的几类区间覆盖问题 nyoj 12喷水装置(二) nyoj 14会场安排问题
1)区间完全覆盖问题 问题描述:给定一个长度为m的区间,再给出n条线段的起点和终点(注意这里是闭区间),求最少使用多少条线段可以将整个区间完全覆盖 样例: 区间长度8,可选的覆盖线段[2,6],[1, ...
- 增强学习贪心算法与Softmax算法
(一) 这个算法是基于一个概率来对探索和利用进行折中:每次尝试时,以概率进行探索,即以均匀概率随机选取一个摇臂,以的概率进行利用,即以这个概率选择当前平均奖赏最高的摇臂(如有多个,则随机选取). 其中 ...
- 【九度OJ】题目1434贪心算法
题目 本题的贪心算法策略需要深入思考一下 看到题目,最初没有理解题目的要求:看尽量多的完整的节目.尽量多是指数量多,自己理解成观看的时间最长.这样想其实简化了这道题. 正确理解题意后,首先想到的想法是 ...
随机推荐
- 将个人PC转变为高效的云电脑:理论、实践与优化
本文分享自天翼云开发者社区<将个人PC转变为高效的云电脑:理论.实践与优化>,作者:不知不觉 在数字化时代的今天,我们越来越依赖互联网和计算机技术进行工作和生活.然而,传统的个人电脑(PC ...
- linux监控系统行为
1.验证电脑是否存在,一般都有 which script /usr/bin/script 2.配置profile文件,在末尾添加如下内容: vim /etc/profile ============= ...
- 在 GitLab CI/CD 中使用内置的容器镜像库
配置 Docker-in-Docker Docker-in-Docker (dind) means: 你应该注册一个 Docker executor 或 Kubernetes executor 执行器 ...
- DeepSeek企业级部署实战指南:从服务器选型到Dify私有化落地
对于个人开发者或尝鲜者而言,本地想要部署 DeepSeek 有很多种方案,但是一旦涉及到企业级部署,则步骤将会繁琐很多. 比如我们的第一步就需要先根据实际业务场景评估出我们到底需要部署什么规格的模型, ...
- 2分钟学会 DeepSeek API,竟然比官方更好用!
大家好,我是程序员鱼皮.最近 DeepSeek AI 太火了,效果也很强,但致命问题是 不稳定, 经常给我返回 服务器繁忙,请稍后再试,甚至让我怀疑自己被杀熟了. 也有网友说,第一次使用成功率很高,第 ...
- c#securityexception不允许所请求的注册表访问权
开机自启动程序如下: if (!System.IO.File.Exists(filename)) throw new Exception("该文件不存在 ...
- 最新版go-cqhttp的sign 签名服务器搭建教程
安装go-cqhttp 传送门 自建sign签名服务器容器: 拉取镜像(只支持amd64) docker pull hansaes/unidbg-fetch-qsign:latest 启动容器 doc ...
- DeepSeek在M芯片Mac上本地化部署
在 Mac 上使用 Ollama 运行 DeepSeek-R1,并通过 Open-WebUI 提供 Web 端访问. 1. 安装 Ollama Ollama官方:https://ollama.com/ ...
- [第一章]ABAQUS CM插件中文手册
ABAQUS Composite Modeler User Manual(zh-CN) Dassault Systèmes, 2018 注: 源文档的交叉引用链接,本文无效 有些语句英文表达更易理解, ...
- Linux常用命令-练习记录
具体命令 1.复制文件到指定目标,若目录不存在则创建目录 mkdir 和 cp 结合使用 mkdir ../dst/sh_test && cp sh_test/hello_os.sh ...