首页 > 其他分享 >贪心(反悔贪心)题单报告

贪心(反悔贪心)题单报告

时间:2023-07-25 23:00:32浏览次数:43  
标签:int long 反悔 que 贪心 我们 题单

Democy 爷给了一份贪心的题单,但是由于我是小笨比,所以很多题我都不是很会做,现在来简单写一份总结,加强一下印象 qwq。

首先什么叫贪心?贪心就是我每次都选择一个最大值。比如说我现在有 \(n\) 个物品,每个物品都有一个价值,我们可以选 \(k\) 件物品,我们怎么样让选择的价值和最大呢?傻子都知道我们要选最大的 \(k\) 个。这就是贪心。

贪心还有很多有趣的地方的说 qwq。

由于我贪心真的很差,所以我可能要拆成三篇写这份题单的报告 awa

反悔贪心

我在网上查了一下反悔贪心的资料,比如这个这个,什么反悔堆反悔自动机的把我看蒙了。我觉得这个东西有一说一全属胡扯八道,完全就是照着题目写模型,比如说啥“双向链表模型啊”完全没有这档子事,是我先有了反悔贪心,然后为了维护反悔贪心,然后才会用堆啊双向链表。我觉得这就是对反悔贪心的理解不透彻,虽然我自个儿理解的也不是很好。这段话语气有点冲希望大家不要怼我(

一句题外话:我之前问了问 democy 爷反悔贪心怎么证明,据说这个要用拟阵,它有一个最优子结构性质在,但是这个东西不适合幼儿园小朋友学习。总结就是你觉得这个反悔贪心看上去很对然后你就可以这么写。(雾

那么反悔贪心是啥,就是有的时候我们贪心不一定是最优解,然后我们允许我们反悔撤销操作来得到最优解。怎么证明似乎要用拟阵,总而言之我不会,就是觉得能用就行。毕竟 OI 是一门没有证明的艺术(雾)。它怎么写?看几道题就明白了。贪心不也一样吗,没有一定的方法套路。

P3545

我们会想到两个贪心策略

  1. 多卖,满足客户要求。
  2. 库存要多

其中,第一个策略是我们要尽量满足的,因为它是答案。第二个策略是为了辅助我们的第一个策略的。有时我们一味的满足第一个条件可能不是最优的选择,比如第一天进了 100 个货,客户要 100 个,第二天第三天都没进,客户只要 1 个货。如果我们只按照 1 的策略,我们就只能满足 1 个人,但是实际上我们可以满足 2 个人。

所以聪明的你就会想到,在贪心中加入一个反悔操作。我们可以把卖 100 个给第一个人这个决策放到一个篮子里面,然后我下面如果能卖就卖(满足条件 1),如果卖不掉呢,我们就考虑反悔,看看如果我们之前不和某个人交易,如果我之前交易的比现在的需求量大,我还不如用之前交易的来满足现在的需,然后把之前满足的“退单”,因为这样我的答案加一减一后不会变,而且库存会变多。这个篮子用啥?很显然,用大根堆。

#include <iostream>
#include <queue>
#include <vector>
#include <algorithm>
#define MAXN 250000
#define int long long
#define QWQ cout << "QWQ" << endl;
using namespace std;
struct node {
	long long val;
	int id;
	node(long long V, int I) {
		val = V, id = I;
	}
	bool operator < (const node &other) const {
		return val < other.val;
	}
};
vector <int> vec;
long long a[MAXN + 10], b[MAXN + 10];
bool vis[MAXN + 10];
signed main() {
	priority_queue <node> que;
	int n; cin >> n;
	for(int p = 1; p <= n; p++)
		cin >> a[p];
	long long ans = 0, tot = 0;
	for(int p = 1; p <= n; p++) {
		cin >> b[p];
		tot += a[p];
		if(tot >= b[p]) {
			tot -= b[p];
			que.push(node(b[p], p));
			ans++;
		}
		else if(!que.empty() && que.top().val > b[p]) {
			tot = tot + que.top().val - b[p];
			que.pop(); que.push(node(b[p], p));
		}
	}
	while(!que.empty()) {
		vec.push_back(que.top().id);
		que.pop();
	}
	sort(vec.begin(), vec.end());
	cout << ans << endl;
	for(int p = 0; p < ans; p++)
		cout << vec[p] << ' ';
}

上面讲的有点抽象,看看代码就懂了 awa

顺带一提,我之前有一个问题 link

这个问题是怎么回事你,就是说我把反悔贪心的判断写成了酱紫 else if(!que.empty() && tot + que.top().val >= b[p]) {
然后我就 WA 了。这很好解释。反悔的目的在于让库存变多。但是如果用剩下的+之前的订单>=需求量,就可能会出现之前的订单<需求量的情况。如果是这种情况,库存会变少,不能反悔。这告诉我们反悔要仔细想一想判断条件是不是满足反悔要求

还有一个朋友提出了酱紫的一个问题 link,就是说如果我每个客户有个满意度,我让满意度最大,那么这还能做吗?我觉得不能。我在帖子下也回复了,现在 copy 过来(

因为这道题我贪心要有两个策略

  1. 多卖,满足客户要求。
  2. 库存要多
    第一个策略要尽量满足,是我们要求的答案,第二个策略是为了第一个策略来制定的,有时候一味的第一个策略不是正确答案,就需要第二个策略来制约。具体可以这么感性理解,怎么证明窝不会,好像要用拟阵反正窝不会,OI 不需要证明(

反悔贪心的反悔建立在 1 2 的共同基础上。反悔反悔什么,反悔在于我之前的决策价值是 1,我反悔后的决策价值也是 1。这样 1 条件是满足的(不会变坏),如果我反悔,我可以让 2 条件更好,所以我会这样做。但是如果带权,1 条件就不一定可以满足,所以我不能反悔贪心。

P2209

经典的旅行商问题!

看到这个问题,第一个想法就是在每个加油站把所有的油给油箱加满,但是这很明显是错的,首先我可能用不完,其次我可能就是刚好用现在有的油走到下一站,然后我用下一站更便宜的油来加。

第一个问题很好解决,只在“走”的时候累加油的费用。第二个问题怎么解决?由于每个站可以无限加油,所以我们可以这样看待我们的油箱:它是分层的,我们每次都用费用最小的油,旅行到下一个站的时候,如果这个站的油很便宜,那么就把油箱里面的所有贵的油退掉。现在的问题就是怎么维护这个油箱。这个退油操作很容易让我们联想到单调队列,是的,我们可以用单调队列维护,时间复杂度 \(O(n)\)。

#include <bits/stdc++.h>
#define int long long
#define QWQ cout << "qwq" << endl;
using namespace std;
const int MAXN = 1e5;
struct node {
	int x, y;
	bool operator < (const node &other) const {
		return x < other.x;
	}
} A[MAXN + 10], que[MAXN + 10];
int n, G, B, D;
void init() {
	int head = 1, tail = 1;
	cin >> n >> G >> B >> D;
	A[1].x = 0, A[1].y = 0;
	for(int p = 2; p <= n + 1; p++)
		cin >> A[p].x >> A[p].y;
	A[n + 2].x = D, A[n + 2].y = 0, n += 2;
	sort(A + 1, A + n + 1);
	que[1].x = 0, que[1].y = B;
	
	int cost = 0;
	for(int p = 2; p <= n; p++) {
		int dist = abs(A[p].x - A[p - 1].x);
		while(head <= tail) {
			if(que[head].y < dist) {
				cost += que[head].x * que[head].y;
				dist -= que[head].y;
				head++;
			}
			else {
				cost += dist * que[head].x;
				que[head].y -= dist;
				dist = 0;
				break;
			}
		}
		if(dist) {
			cout << -1 << endl;
			return ;
		}
		
		int num = ((p == 2) ? (G - B + abs(A[p].x - A[p - 1].x)) : (abs(A[p].x - A[p - 1].x)));
		while(head <= tail) {
			if(que[tail].x >= A[p].y) {
				num += que[tail].y;
				tail--;
			}
			else break;
		}
		que[++tail].x = A[p].y;
		que[tail].y = num;
	}
	cout << cost << endl;
}
signed main() {
	init();
}

P1792
种树,超级经典的反悔贪心的说 qwq

一般来讲,让你求最大或小的 \(K\) 个,那么会用一个二叉堆维护。而且每次求出来堆顶后,会对堆顶进行一个“分裂”,扩展出其它的选择。比如这题。P2048 超级钢琴也是这样的一个思路 qwq。

首先考虑 \(K = 1\) 的情况,很显然我们会取最大的一个 \(A_i\)。

考虑完了这种情况,下面我们考虑 \(K = 2\)。除了取单个的一个 \(A_i\),我们还可能会有什么操作呢?先不考虑环的一些细节,像酱紫

* * * i-1 i i+1 * * *

对于此时的决策,我们有三种可能

  1. \(A_1\sim A_{i - 2}\).
  2. \(A_{i+2}\sim A_n\)
  3. 选 \(A_{i - 1}, A_{i+1}\) 而不选 \(A_i\)。

注意第三种是有可能的,在不考虑环的情况下,比如是这样的序列

1 1 1 1 3 4 3 1 1 1

第一次我们会选 \(4\),但是第二次只能选 \(1\),这显然不如选它旁边的 \(3, 3\)。

第一个和第二个可能很好维护(之前已经在大根堆里面放过了),那么第三个怎么维护呢?由于我们已经做好了 \(A_i\) 的贡献,那么这个时候我们考虑退回 \(A_i\) 的贡献,也就是说把 \(A_{i - 1}+ A_{i+1} - A_i\) 放进大根堆里面。这相当于把 \(A_{i-1}, A_{i+1}, A_i\) 合并了。合并成了一个新元素。这很容易证明,我们只会这么搭配它们俩。

这样合并,那么我们的左边就可能不是 \(i-1\) 右边可能不是 \(i+1\),因为合并过了,那么怎么办呢?我们可以记录 \(left_i\) 代表左边的,\(right_i\) 代表 \(i\) 右边的。环也很好通过 \(left\) 和 \(right\) 体现。\(vis_i\) 代表 \(i\) 有没有被合并过。每次都用大根堆取出堆顶 \(a_i\),加入贡献,合并 \(a_i = a_{i-left}+ a_{i+right} - a_i\),顺便修改三者的 \(left, right\)。特别注意注意,如果 \(i\) 被合并过了,那么我们就不会处理。这很好理解:用归纳法,如果我们前面做的一定是最优解,而且根据贪心的最优子结构,所以我这一步一定是在之前的步上才能走出来最优解。否则这题就不能贪心了。

更加严谨的证明我真的不会呜呜呜呜不会证反悔贪心的最优子结构,不过可以这样记:dp 和贪心的最优子结构使我们不用去额外考量之前所做的操作是否会影响到现在的操作,如果会,那么这道题就不能用 dp 和贪心

#include <iostream>
#include <queue>
#define MAXN 1000000
#define QWQ cout << "QWQ" << endl;
using namespace std;
int a[MAXN + 10];
int l[MAXN + 10], r[MAXN + 10];
struct node {
	int id, val;
	node(int I, int V) {
		id = I, val = V;
	}
	bool operator < (const node &other) const {
		return val < other.val;
	} 
};
bool vis[MAXN + 10];
int main() {
	priority_queue <node> que;
	int n, m; cin >> n >> m;
	if(m * 2 > n) {
		cout << "Error!" << endl;
		return 0;
	}
	for(int p = 1; p <= n; p++) {
		cin >> a[p];
		int L, R;
		if(p == 1) l[p] = n, r[p] = 2;
		else if(p == n) l[p] = n - 1, r[p] = 1;
		else l[p] = p - 1, r[p] = p + 1; 
		que.push(node(p, a[p]));
	}
	int ans = 0;
	for(int p = 1; p <= m; p++) {
		while(vis[que.top().id]) que.pop();
		int qwq = que.top().id;
		vis[l[qwq]] = 1, vis[r[qwq]] = 1;
		ans += que.top().val;
		a[qwq] = a[l[qwq]] + a[r[qwq]] - a[qwq];
		que.push(node(qwq, a[qwq]));
		que.pop();
		l[qwq] = l[l[qwq]], r[qwq] = r[r[qwq]];
		l[r[qwq]] = qwq, r[l[qwq]] = qwq;
	}
	cout << ans << endl;
}

顺便一提什么“双向链表模型”又是什么鬼哦,这道题双向链表只是为了方便叙述,本质上是维护左右的编号,这个模型属实是对着题目瞎讲了(雾

标签:int,long,反悔,que,贪心,我们,题单
From: https://www.cnblogs.com/thirty-two/p/17574539.html

相关文章

  • 左神算法-基础06-前缀树&贪心算法
    左神算法-基础06-前缀树&贪心算法介绍前缀树何为前缀树?如何生成前缀树?例子:一个字符串类型的数组arr1,另一个字符串类型的数组arr2。arr2中有哪些字符,是arr1中出现的?请打印。arr2中有哪些字符,是作为arr1中某个字符串前缀出现的?请打印。arr2中有哪些字符,是作为arr1中某个......
  • 第二周训练题单
    多项式输出小细节比较多#include<bits/stdc++.h>usingnamespacestd;#defineintlonglongint32_tmain(){ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);intn;cin>>n;for(intx,i=n;i>=0;i--){......
  • 代码随想录贪心专题-day1
    35.分发糖果n个孩子站成一排。给你一个整数数组ratings表示每个孩子的评分。你需要按照以下要求,给这些孩子分发糖果:每个孩子至少分配到1个糖果。相邻两个孩子评分更高的孩子会获得更多的糖果。请你给每个孩子分发糖果,计算并返回需要准备的最少糖果数目。思路:本题这......
  • 2023夏季集训D1-贪心二分
    2023夏季集训D1贪心二分0x00前言24OIFXJ大佬来给我们讲课OrzOrz.讲课好难TAT.0x10贪心0x11经典贪心写了BestCowLineG/S和田忌赛马一位小伙从同学那里要来了一份BestCow代码Debug但没有发现代码没有输入,这是他思路3小时后发生的hack.田忌赛马太......
  • poj 1716 Integer Intervals (贪心)
    题意:给定n个连续的区间,求一个集合。其中,n个区间的每一个区间都至少包含两个这个集合的元素。求这个集合元素的最小值。 题解:1、先将所有区间按终点从小到大排序。2、我们先取第一个区间(排好序的区间)的两个值,因为要使得结果最优,我们肯定选择最接近终点的那两个值。假设一个为Selem,......
  • 【贪心】AGC018C Coins
    ProblemLink现在有\(X+Y+Z\)个人,第\(i\)个人有三个权值\(a_i,b_i,c_i\),现在要求依次选出\(X\)个人,\(Y\)个人和\(Z\)个人(一个人只能选依次),使得这\(X\)个人的\(a\)权值,\(Y\)个人的\(b\)权值,\(Z\)个人的\(c\)权值之和最大。\(X,Y,Z\le10^5\)。技巧:排序证明......
  • abc085d <贪心>
    题目D-KatanaThrower思路关键:连续使用ai与投掷bi并无冲突,可先使用ai再投掷bi找到ai中的最大值maxa;首先从大到小使用bi中比maxa大的元素,而后不足h再重复使用maxa(虽然按照先b后a的顺序分析计算,但实际上应是先用a后用b)代码Code//https://atcoder.jp/conte......
  • abc083d <思维 贪心>
    题目D-WideFlip思路参考live4m的博客其实全0和全1是无所谓的,只需要全部相同就行了,因为每次操作是令一个>=k区间的翻转,如果是全1,令[1,n]再翻转一次即可.考虑[1,i]已经相同,s[i]!=s[i+1]时如何操作,要使得[1,i+1]相同,要么[1,i]翻转,要么[i+1,n]翻转,为了使k最大,显......
  • 贪心题目合集
    CF626G题目链接比较简单的贪心。首先不去考虑修改操作,注意到这个条件我们可以看作有若干个物品,选取每个物品有\(1\)的代价和某个价值。有若干个限制条件是要选某一个必须要先选某个价值比他大的东西。根据贪心的原理这个限制条件其实跟没有一样,因为你不会先选价值小的东西。......
  • 神必贪心合集/fn
    CF1601DDifficultMountainhttps://www.luogu.com.cn/problem/CF1601D一道神必贪心首先我们分类考虑贪心的几种情况对于两个人\(i\)与\(j\),并且两人都满足s>p\(1.s[i]<a[i]\)\(\space\space1)\)\(a[i]<s[j]<a[j]\)显然\(i\)在\(j\)前更优\(\space\space2)\)\(a[i......