算法基础篇(8)贪心算法

贪心算法,或者说是贪心策略,就是企图用局部最优找出全局最优。

1.把解决问题的过程分为若干步

2.解决每一步时,都选择"当前看起来最优的"解法

3."希望"得到全局的最优解

8.1 简单贪心

8.1.1 货仓选址

根据贪心的思想,我们最希望把仓库建在中间位置。

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

const int N = 1e5 + 10;
int n;
long long a[N];

int main()
{
	cin >> n;
	for (int i = 1;i <= n;i++)
	{
		cin >> a[i];
	}

	sort(a + 1, a + 1 + n);

	long long ret = 0;

	for (int i = 1;i <= n;i++)
	{
		ret += abs(a[i] - a[(1 + n) / 2]);
	}
	cout << ret << endl;

	return 0;
}

那么,这个贪心策略是否就是本题的最优解呢?下面开始证明:

这里就要用到数学上的绝对值不等式:|a-x| + |b - x| ≥ |a-b|

根据上面的出来的公式,这道题的代码还可以这么写:

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

const int N = 1e5 + 10;
int n;
long long a[N];

int main()
{
	cin >> n;
	for (int i = 1;i <= n;i++)
	{
		cin >> a[i];
	}

	sort(a + 1, a + 1 + n);

	long long ret = 0;

	/*for (int i = 1;i <= n;i++)
	{
		ret += abs(a[i] - a[(1 + n) / 2]);
	}*/

	for (int i = 1;i <= n / 2;i++)
	{
		ret += a[n + 1 - i] - a[i];
	}

	cout << ret << endl;

	return 0;
}

8.1.2 最大子段和

根据贪心的思想,我们希望sum ≥ 0,就继续向后累加;sum < 0,就舍弃这一段,从下个位置开始重新累加。

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;

const int N = 2e5 + 10;
int n;
long long a[N];

int main()
{
	cin >> n;
	for (int i = 1;i <= n;i++)
		cin >> a[i];

	long long ret = -1e6, sum = 0;

	for (int i = 1;i <= n;i++)
	{
		sum += a[i];
		ret = max(ret, sum);
		if (sum < 0)
			sum = 0;
	}

	cout << ret << endl;

	return 0;
}

下面证明该贪心思想是正确的:

8.1.3 纪念品分组

贪心策略:在剩余物品中,取出最小值x和最大值y,如果x + y ≤ w,就放在一起;如果x + y > w,就把y单独放,x待定。

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 3e4 + 10;
int n, w;
int a[N];

int main()
{
	cin >> w >> n;
	for (int i = 1;i <= n;i++)
		cin >> a[i];

	sort(a + 1, a + 1 + n);

	int i = 1, j = n;
	long long cnt = 0;

	while (i <= j)
	{
		if (a[i] + a[j] <= w)
			i++;

		cnt++;
		j--;
	}

	cout << cnt << endl;

	return 0;
}

8.2 公式法

细说的话,这里应该是推公式+排序。其中推公式就是寻找排序规则,排序就是在该排序规则下对整个对象排序。在解决某些问题时,当我们发现最终结果需要调整每个对象的先后顺序,那么就可以使用推公式的方式来得出排序规则,进而对整个对象进行排序。但是这里的证明过程会很麻烦(需要用到离散数学中的"全序关系"),后续题目中我们只要发现该题的最终结果需要排序,并且交换相邻两个元素的时候对其余元素不会产生影响,那么就可以推导出排序的规则,然后直接去排序,就不用证明了。

8.2.1 拼数

直接拼接,然后确定先后关系。ab > ba,a前b后;ab < ba,b前a后。

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 30;
int n;
string a[N];

bool cmp(string& x, string& y)
{
	return x + y > y + x;
}

int main()
{
	cin >> n;
	for (int i = 1;i <= n;i++)
		cin >> a[i];

	sort(a + 1, a + 1 + n, cmp);

	for (int i = 1;i <= n;i++)
		cout << a[i];

	return 0;
}

8.2.2 保卫花园

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1e5 + 10;
int n;

struct node
{
	int t;
	int d;
}a[N];

bool cmp(node& x, node& y)
{
	return x.t * y.d < y.t * x.d;
}

int main()
{
	cin >> n;
	for (int i = 1;i <= n;i++)
		cin >> a[i].t >> a[i].d;

	sort(a + 1, a + 1 + n, cmp);

	long long sum = 0, t = 0;

	for (int i = 1;i <= n;i++)
	{
		sum += a[i].d * t;
		t += 2 * a[i].t;
	}

	cout << sum << endl;

	return 0;
}

8.3 区间问题

区间问题是另一种比较经典的贪心问题。题目面对的对象是一个一个的区间,让我们在每一个区间上做出取舍。这种题目的解决方式一般就是按照区间的左端点或者是右端点排序,然后在排序区间上,根据题目要求制定出贪心策略,进而得到最优解。

8.3.1 线段覆盖

这道题的本质就是有若干个区间,让我们尽可能多的选择一些区间,使它们之间互不重叠。面对区间问题,第一步先排序。排序分为两种------左端点升序/降序 以及 右端点升序/降序。这里我们按照左端点升序排列。当区间重叠的时候,尽可能保留右端点较小的区间;当区间不重叠的时候,以下一个区间为基准,继续判断。

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1e6 + 10;
int n;

struct node
{
	int left;
	int right;
}a[N];

bool cmp(node& x, node& y)
{
	return x.left < y.left;
}

int main()
{
	cin >> n;
	for (int i = 1;i <= n;i++)
		cin >> a[i].left >> a[i].right;

	sort(a + 1, a + 1 + n, cmp);

	int cnt = 1;
	int r = a[1].right; //以第一个区间为基准,向后选择
	for (int i = 2;i <= n;i++)
	{
		int left = a[i].left;
		int right = a[i].right;
		if (left < r)  //重叠
		{
			r = min(r, right);
		}
		else
		{
			cnt++;
			r = right;
		}
	}

	cout << cnt << endl;

	return 0;
}

8.3.2 雷达安装

这道题如果直接在二维平面内分析的话会十分复杂,因为我们要枚举x轴上所有雷达的位置,然后计算每座岛屿到雷达的距离。所以第一步也是最关键的一步,就是把二维转化为一维。针对一座岛屿,当我们想要覆盖这座岛屿的时候,x轴上雷达的极限位置应该在哪一段区间内。第二步,在所有区间中,找出互相重叠的一共有多少组。先根据左端点排序,互相重叠的区间是连续的。当两个区间重叠时,应该用右端点较小的区间继续往后找。不重叠时,以下一个区间为基准,继续往后找。

cpp 复制代码
#define  _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;

const int N = 1010;
int n;
double d;

struct node
{
	double l;
	double r;
}a[N];

bool cmp(node& x, node& y)
{
	return x.l < y.l;
}

int main()
{
	int cnt = 0;
	while (cin >> n >> d, n && d)
	{
		cnt++;
		bool flag = false;

		for (int i = 1;i <= n;i++)
		{
			double x, y;
			cin >> x >> y;
			if (y > d)
				flag = true;

			double l = sqrt(d * d - y * y);
			a[i].l = x - l;
			a[i].r = x + l;
		}

		cout << "Case " << cnt << ": ";
		if (flag)
			cout << -1 << endl;
		else
		{
			sort(a + 1, a + 1 + n, cmp);
			int ret = 1;
			double r = a[1].r;
			for (int i = 2;i <= n;i++)
			{
				double left = a[i].l;
				double right = a[i].r;
				if (left <= r)  //有重叠
				{
					r = min(r, right);
				}
				else
				{
					ret++;
					r = right;
				}
			}
			cout << ret << endl;
		}
	}

	return 0;
}
相关推荐
顾安r5 小时前
11.15 脚本算法 加密网页
服务器·算法·flask·html·同态加密
前端小L5 小时前
图论专题(四):DFS的“回溯”之舞——探寻「所有可能路径」
算法·深度优先·图论
司铭鸿5 小时前
数学图论的艺术:解码最小公倍数图中的连通奥秘
运维·开发语言·算法·游戏·图论
元亓亓亓6 小时前
LeetCode热题100--39. 组合总和
算法·leetcode·职场和发展
2401_841495646 小时前
【LeetCode刷题】找到字符串中所有字母异位词
数据结构·python·算法·leetcode·数组·滑动窗口·找到字符串中所有字母异位词
橘颂TA6 小时前
【剑斩OFFER】算法的暴力美学——寻找数组的中心下标
算法·leetcode·职场和发展·结构与算法
py有趣6 小时前
LeetCode算法学习之鸡蛋掉落
学习·算法·leetcode
放羊郎6 小时前
机器人自主导航方案概述
人工智能·算法·机器人·slam·建图
冷徹 .6 小时前
Edu144 CD
c++·算法
一水鉴天7 小时前
整体设计 全面梳理复盘 之37 元级自动化引擎三体项目(Designer/Master/Transformer)划分确定 + 自用规划工具(增强版)
开发语言·算法·transformer·公共逻辑