刷题之路(第三周)(贪心)

这周重温下贪心,众所周知,贪心就是只要胆子大,你就会发现,诶嘿~自己又超时了

所以说,贪心的这类题目个人觉得最需要注意的就是两点,一个是贪什么,也就是,题目要我们干什么;另一个是如何优化自己贪心策略,也就是剪枝。

当然啦,贪心可以说是一个比较容易想出来的方法,而题目往往又不会是那么的友善,所以,在自己用贪心始终过不了题目的时候,另寻他路也许是一个更好的选择。

好咧,家人们,首先这个第一题还是有操作的,上来就给我整得一愣一愣的。01背包,啊这,这难道不算是在动态规划里面的题目嘛?

【深基12.例1】部分背包问题 - 洛谷

那这边正好回忆一下背包类型的题目,如果后面有正式开始刷动态规划的题目,就在详细讲解。

 背包问题主要是分成01背包、完全背包和混合背包问题。

这里很明显就是一个01背包,只装一种状态的东西。

那自然是根据板子来解题目啦~

首先是了解最重要的几步:1、dp[i][j]表示遍览到第i件时背包重量为j时有最大价值

                                           2、对于每一种物品,又有着装与不装两种状态,那自然是

                                                 dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i])//不装与装

哦对了,记得把dp[0][0~W]初始为0,代表将前0个(即没有)物品装进背包时,dp的价值为0

然后就是表儿打起,结束后输出对应数值就搞定啦~

嗯...搞定个毛线,我都把这题敲完了才发现,它...根本不是背包问题!

服了我自己,注意看题目“金币可以任意分割”,所以说根本不存在装不下的问题

所以只需要排序它们的单位价值量就能找到最大的价值量

这其实就是一道水题......

#include<iostream>
#include<stdio.h>
#include<iomanip>
#include<algorithm>
#include<math.h>
using namespace std;
struct Node{
	double w,v,k;
}t[500];
bool cmp(Node a, Node b) {
	return a.k > b.k;
}
int main() {
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> t[i].w >> t[i].v;
		t[i].k = t[i].v / t[i].w;
	}
	sort(t + 1, t + 1 + n, cmp);
	double sum = 0;
	for (int i = 1; i <= n; i++) {
		if (m >= t[i].w) {
			sum = sum + t[i].v;
			m = m - t[i].w;
		}
		else {
			sum = sum + t[i].k * m;
			break;
		}
	}
	printf("%.2f", sum);
	return 0;
}

还真是,贪心呢...呵呵TAT

然后就都是一些能用sort做的排序问题,这个没什么好多说的。

哦,有些母题还是记一下

凌乱的yyy / 线段覆盖 - 洛谷

要取最多的线段,最重要的思维是将这些线段的右端点从左往右,这样能够放进去的线段组成的就是最多的线段数

#include<iostream>
#include<stdio.h>
#include<iomanip>
#include<algorithm>
#include<math.h>
using namespace std;
const int mymax = 1e6 + 5;
struct Node {
	int l, r;
}a[mymax];
bool cmp(Node a, Node b) {
	return a.r < b.r;
}
int main() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> a[i].l >> a[i].r;
	}
	sort(a + 1, a + 1 + n, cmp);
	int sum = 0;
	int t = -1;
	for (int i = 1; i <= n; i++) {
		if (t <= a[i].l) {
			t = a[i].r;
			sum++;
		}
	}
	cout << sum;
	return 0;
}

然后就是一道让我意识到自己优先队列忘了个干净的题目

[NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G - 洛谷

这题题意其实很简单,就是说让你每次挑出最小重量的果堆合起来再放到所有的果堆中去,直至只剩一个果堆,所有要维护队列始终为从小到大或者从大到小,这里自然想到要用优先队列了啦~

然后这边关于优先队列的使用可以看下其他大佬的帖子,我这里就不多说了,以免误导各位~

最后发下AC代码,供各位大佬参考

#include<iostream>
#include<stdio.h>
#include<iomanip>
#include<algorithm>
#include<math.h>
#include<stack>
#include<queue>
using namespace std;
priority_queue<int, vector<int>, greater<int>>p;
int main() {
	int n;
	cin >> n;
	int t;
	while (n--) {
		cin >> t;
		p.push(t);
	}
	int sum = 0;
	while (p.size() != 1) {
		int b = p.top();
		p.pop();
		int a = p.top();
		p.pop();
		sum = sum + a + b;
		p.push(a + b);
	}
	cout << sum;
	return 0;
}

然后,是一道入门级的贪心问题,又被特判坑了一波TAT

小A的糖果 - 洛谷

分析步骤如下:观察此题,1、重复操作;2、有迹可循;3、大可化小

吃糖为重复操作,且处理的问题又是相同的,处理n盒糖的过程实际就是处理3盒糖的不断重复过程,所以可以推出此题可用贪心的办法解题

然后就是,注意特判!

#include<iostream>
#include<stdio.h>
#include<iomanip>
#include<algorithm>
#include<math.h>
#include<stack>
#include<queue>
using namespace std;
const int mymax = 1e8 + 5;
long long a[mymax];
int main() {
	int n,m;
	cin >> n>>m;
	long long sum = 0;
	cin >> a[1];
	if (a[1] > m) {
		sum = sum + a[1] - m;
		a[1] = m;
	}
	for (int i = 2; i <= n; i++) {
		cin >> a[i];
		if (a[i] + a[i - 1] > m) {
			sum = sum+a[i] + a[i - 1] - m;
			a[i] = m - a[i - 1];
		}
	}
	cout << sum;
	return 0;
}

继续往下,往下都是一些很简单的贪心问题,有一道填坑题还蛮有趣的,这里记录一下

​​​​​​[NOIP2018 提高组] 铺设道路 - 洛谷

#include<iostream>
#include<stdio.h>
#include<iomanip>
#include<algorithm>
#include<math.h>
#include<stack>
#include<queue>
#include<string>
using namespace std;
int a[1000000];
int main() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	int sum = 0;
	for (int i = 2; i <= n; i++) {
		//很重要的一步,多个坑的最优方案实际就是多个两个坑的最优方案
		if (a[i] > a[i - 1]) {//右边的坑比左边的坑大,所以左边的坑就自动填好了
			sum = sum + a[i] - a[i - 1];//需要多填的部分,脑补一下
		}
		//如果右边的坑比左边的坑小,那么在处理上一个方案时这个坑就已经处理好了,所以不需要额外处理
	}
	sum = sum + a[1];
	cout << sum;
	return 0;
}

然后就是卡了我的一题

跳跳! - 洛谷

当时的第一反应就是dp搜索,然后也确实这么写了一个,只拿了10分的代码,害

#include<iostream>
#include<stdio.h>
#include<iomanip>
#include<algorithm>
#include<math.h>
#include<stack>
#include<queue>
#include<string>
using namespace std;
const int mymax = 500;
long long a[mymax],p[mymax];
long long sum = -1;
int n, m;
void dp(int i, int k,int ans) {
	if (k == n) {
		if (ans >= sum) {
			sum = ans;
		}
		return;
	}
	else {
		p[i] = 1;
		for (int j = 1; j <= n; j++) {
			if (p[j] != 1) {
				p[j] = 1;
				dp(j, k + 1, ans + (a[i] - a[j]) * (a[i] - a[j]));
				p[j] = 0;
			}
		}
		p[i] = 0;
	}
	return;
}
int main() {
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	for (int i = 1; i < n; i++) {
		dp(i, 1,a[i]*a[i]);
	}
	cout << sum;
	return 0;
}

 发现疯狂TL,说明这个方法得优化,不能通过所有路径找最大路径的方法求解。

然后想了想,诶呀,我的天,找最大的落差,那不就是每次都从最小跳最大再从最大跳最小嘛?

然后呢......我发现这题好水啊

AC代码如下:

#include<iostream>
#include<stdio.h>
#include<iomanip>
#include<algorithm>
#include<math.h>
#include<stack>
#include<queue>
#include<string>
using namespace std;
const int mymax = 500;
long long a[mymax],p[mymax];
long long sum = 0;
int n, m;

int main() {
	cin >> n;
	a[0] = 0;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	sort(a , a + 1 + n);
	int l = 0, r = n;
	int k = 1;
	while (l <= r) {
		if (k == 1) {
			sum = sum + (a[r] - a[l]) * (a[r] - a[l]);
			l++;
			k = 2;
		}
		else if (k == 2) {
			sum = sum + (a[r] - a[l]) * (a[r] - a[l]);
			r--;
			k = 1;
		}
	}
	cout << sum;
	return 0;
}

然后就是我觉得贪心问题的一类很重要的问题了,也就是题目会让你在满足某些条件的情况下实现另外一个额外的条件

题单中最后两道题都是这种类型

[AHOI2018初中组]分组 - 洛谷

像这题,就是要你在满足分组要求的情况下还使得人数最少的那一组人数最大。

这题难度还是挺高的,最开始想到的贪心方法时,先从小到大排,一旦相差不是1就输出,这样就可以确保是最小的长度,然后发现最后几个检查点都报错了,学习了下各位大佬们的思路,这题好像可以用栈来存储从而进行优化。

AC代码:

#include<iostream>
#include<stdio.h>
#include<iomanip>
#include<algorithm>
#include<math.h>
#include<stack>
#include<queue>
#include<string>
using namespace std;
long long t[100005], n;
const int mymax = 100005;
int mymin = 99999999;
stack<int>q[mymax];
int main() {
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> t[i];
	}
	sort(t + 1, t + n + 1);
	int p = 0;
	int k = 0;//k表示当前有多少个栈
	for (int i = 1; i <= n; i++) {
		for (int j = k; j >= 1; j--) {
			if (q[j].top() + 1 == t[i]) {
				q[j].push(t[i]);
				p = 1;
				break;
			}
			p = 0;
		}
		if (p == 0) {
			k++;
			q[k].push(t[i]);
		}
	}
	for (int i = 1; i <= k; i++) {
		if (mymin >= q[i].size())mymin = q[i].size();
	}
	cout << mymin;
	return 0;
}

很迷惑诶,感觉和数组的思维是一样的,但最后几个测试点就过了。

然后就是经典题目:国王游戏

[NOIP2012 提高组] 国王游戏 - 洛谷

这道题也是一个很难想的题目,首先要理解一下题意,排在队伍的第n个人能获得的金币是之前所有人左手上数的乘积除以这个人右手上的数字。题目要求这n个人中能获得的金币数最大的人手上的金币尽可能小。

首先就是,对于重复的复杂问题要将其简化过程,这题要排n个人的队,实际就是排多次两个人的队,那么取两个人进行分析,并且目标是使得金币多的金币尽可能少。

明确以上几点后往下分析,可以得出当a1*b1>a2*b2时,b2在前,所以推出当所有人按ai*bi从小到大的顺序排列时就可以得出题目需要的答案。

然后附上我的60分代码,这题要用高精度才能拿满分TAT

#include<iostream>
#include<stdio.h>
#include<iomanip>
#include<algorithm>
#include<math.h>
#include<stack>
#include<queue>
#include<string>
using namespace std;
struct Node {
	int a, b;
}p[10005];
int n;
bool cmp(Node t1, Node t2) {
	return t1.a * t1.b < t2.a* t2.b;
}
int main() {
	cin >> n;
	cin >> p[0].a >> p[0].b;
	for (int i = 1; i <= n; i++)
	{
		cin >> p[i].a >> p[i].b;
	}
	sort(p + 1, p + 1 + n, cmp);
	long long mymax = -1;
	long long temp = p[0].a;
	int t;
	for (int i = 1; i <= n; i++) {
		t = temp / p[i].b;
		temp = temp * p[i].a;
		if (mymax < t)mymax = t;
	}
	cout << mymax;
	return 0;
}

至此,这个题单也完成啦~

总结一下,贪心问题没有什么技巧,主要的是你能不能发现你做的这道题目是贪心

也就是,大量重复的工作,明确的要求,可简化的步骤(个人看法)

那么,下次见咯~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值