贪心专题(一)

写在前面

暑假校内集训的贪心专题
持续更新中…

CodeForces - 442C Artem and Array
【题目描述】

传送带
每个数的贡献是它相邻数的最小的那一个,每次删一个,求最终的贡献最大
左右端点可以看成贡献值为0

【思路】

一开始的贪心思路是每一次找min(a,b)最大的那个数删掉,但如果是n2 很显然就超时了,但每一个数都不大,不超过1e6,所以可以用类似桶的方法来记录每个数的min(a,b),而且由于所有数都在原序列中,所以每删去一个最大值,之后的最大值一定比前一个小,所以最大值是单减的,所以复杂度是On的,每删去一个数只需要修改和它相邻的两个数的min(a,b),可以用map来存
然而这个方法写的我自闭了,而且不小心看到了题解,于是就用了题解的方法

题解也是用贪心做的,贪心思路大致一样,重点是优化时间复杂度,其实纵观整个序列,我们可以不在意删数的次序,而只看删哪些点。将原序列看作一个折线图,三点连线只有“单增”“单减”“有凹点”"有凸点"四种情况,如果全是单调的那么肯定是取最小的n-2个数,有凹点时一定是删去凹点最优(可以当作只剩3个点,粗略证明),模仿凸包(其实不是凸包,因为可以一边向外凸另一边向里凸)来进出栈(最终至多一个最高点),最后把栈里所有数排序取最小的n-2个

【代码】
#include <iostream>
#include <stack>
#include <stdlib.h>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using namespace std;
typedef long long ll;
const int MAXN=5e5+10;
int a[MAXN];
int b[MAXN];
int cnt;
int n;
ll sum;
stack<int> s;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	a[0]=0;
	s.push(a[0]);
	for(int i=1;i<=n;i++){
		int u=s.top();
		s.push(a[i]);
		int v=s.top();
		while(u>=v&&a[i+1]>=v){
			sum+=min(u,a[i+1]);
			s.pop();
			if(s.size()<2) break;
			v=s.top();
			s.pop();
			u=s.top();
			s.push(v);
		}
	}
	while(!s.empty()){
		int u=s.top();
		b[cnt++]=u;
		s.pop();
	}
	sort(b,b+cnt);
	for(int i=0;i<cnt-2;i++)
	    sum+=b[i];
	printf("%lld\n",sum);
	return 0;
} 
CodeForces-1132C Painting the Fence
【题目描述】

长度为n的篱笆,选择给定q个油漆工中的q-2个,使得最终被涂色的篱笆长度最长
传送带

【思路】

其实这题看上去像贪心,用到的却并不是贪心的做法,从q个里面选q-2个这个条件非常特殊,如果枚举的话只需要枚举O(q2),但对每种情况的计数如果直接暴力的话时间复杂度会达到O(nq2),所以要优化。
首先很容易想到需要一个长度为n的数组来保存每个数被覆盖了几次,而且可以用差分数组来实现区间的加减(更新为O(1),查询为O(n))。由于选两个不选的油漆工,所以删去后为0的情况只有两种1)只被覆盖一次,删去的恰好为那一次 2)只被覆盖两次,删去的恰好为那两次
一开始用了两棵线段树(让更新和查询的复杂度都变为O(lgn)),分别维护区间中被覆盖次数为1的个数和被覆盖次数为2的个数,对于任意两个区间(代号为i j),重叠部分为l=后者的左端点 r=前者的右端点,结果T了第19个点
然后我又看了标算TAT 实际上被覆盖次数为1和被覆盖次数为2可以化归成一个问题,先删掉一个人以后就只剩下

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值