ET算法【单调栈与单调队列】

一、序

周日就要打新生赛了,但是本蒟蒻越来越菜了怎么办,一天天的也不知道在忙什么,噢肯定不是因为天天在积你的破分(为什么留了一半的积分要把剩下那一半的积分也积了呢?!!)。积分积的我想死,低等人为什么要学高等数学啊。所以咱还是来敲点代码吧,结果发现啥啥都不会,别太抽象啊,我会破防的。

单调栈与单调队列维护了内部元素的单调性,多用while语句进行处理,以维护单调性。可在发现单调性后用于优化多重背包,将O(n^{2})优化至O(nlogn)。下面几个题核心思想皆为用一个更优的元素代替之前的元素,不要的元素就pop出去。

二、单调栈

P1082 - 【模板】单调栈 - ETOJ (eriktse.com)

读入一个新元素后,与栈顶进行比较,如果栈顶大于等于就pop出去,这样维护了栈顶元素是离得最近的且小于的,判断完后再将新的元素push进去。

#include<bits/stdc++.h>
using namespace std;
#define qio ios::sync_with_stdio(0), cin.tie(0),cout.tie(0);
const int N = 2e5 + 10;
int a[N];
int main() {
	qio
	int n; cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	stack<int> stk;
	for (int i = 1; i <= n; i++) {
		while (stk.size() && stk.top() >= a[i]) stk.pop();
		cout << (stk.size() ? stk.top() : -1) << " ";
		stk.push(a[i]);
	}
}

三、单调队列

P1084 - 滑动窗口 - ETOJ (eriktse.com)

deque<int> dq 双端队列,front和end都可以进出

队头与队尾: front  ···········  end

#include<bits/stdc++.h>
using namespace std;
#define qio ios::sync_with_stdio(0), cin.tie(0),cout.tie(0);
const int N = 2e5 + 10;
int a[N];
int main() {
	qio
	int n, k;	cin >> n >> k;
	for (int i = 1; i <= n; i++) cin >> a[i];
	deque<int> dq;//存放的是下标
	//先求最大值
	for (int i = 1; i <= n; i++) {
		//i为右端点,区间为[i-k+1,i]
		//队头合法性,dq不为空时,将超出范围的都pop出去
		while (dq.size() && dq.front() <= i - k) dq.pop_front();
		//队尾优越性,dq不为空时,将dq里面比a[i]小的都pop出去
		//此时dq中都是比a[i]大的,同理上一次dq中都是比a[i-1]大的,所以队头是最大的
		while (dq.size() && a[dq.back()] <= a[i]) dq.pop_back();
		dq.push_back(i);
		if (i >= k) cout << a[dq.front()] << ' ';
	}
	cout << '\n';
	//清空
	dq = deque<int>();
	//再求最小值
	for (int i = 1; i <= n; i++) {
		while (dq.size() && dq.front() <= i - k) dq.pop_front();
		while (dq.size() && a[dq.back()] >= a[i]) dq.pop_back();
		dq.push_back(i);
		if (i >= k) cout << a[dq.front()] << ' ';
	}
}

四、应用

1.求和

P1093 - 求和 - ETOJ (eriktse.com)

思维题,元素的贡献值,一个最小值常常是一个区间段内子区间的最小值,因此只用找出这一段中的最小值即可(图我就不画了,偷两张来)

但要是遇到相等的最小值该怎么办呢?设定一个参考标准即可,比如都以右边的为标志

问题转化为求左边最近<a[i]的位置,和右边最近<=a[i]的位置

#include<bits/stdc++.h>
using namespace std;
#define qio ios::sync_with_stdio(0), cin.tie(0),cout.tie(0);
typedef long long ll;
const int N = 1e5 + 10;
ll a[N], l[N], r[N]; //后两个存储下标
int main() {
	int n;  cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	stack<int> stk;//存储下标
	for (int i = 1; i <= n; i++) {
		while (stk.size() && a[stk.top()] >= a[i]) stk.pop();
		if (stk.size()) l[i] = stk.top();
		else l[i] = 0;
		stk.push(i);
	}
	while (stk.size())stk.pop(); //清空
	//反向遍历求右侧范围
	for (int i = n; i >= 1; i--) {
		while (stk.size() && a[stk.top()] > a[i]) stk.pop();
		if (stk.size()) r[i] = stk.top();
		else r[i] = n + 1;
		stk.push(i);
	}
	ll ans = 0;
	for (int i = 1; i <= n; i++) {
		//l[]存储区间左端下标,r[]存储区间右端下标
		//两者到i的距离即左右端可能的取值情况
		ans += a[i] * (i - l[i]) * (r[i] - i);
	}
	cout << ans;
}

-->What if     0 < a[i] < 1e3 呢?n为1e5,远远多于a的1e3,说明有大量的重复元素(平均100个),可考虑使用桶

2.切蛋糕

P1714 切蛋糕 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

前缀和与单调队列

虽然我还没做对呢,但是我要去看十佳歌手大赛了,哈哈哈哈哈哈哈,今天就先写到这里吧,一天能写这么多已经是超出预期了


第一步进行前缀和优化,之后就是求在区间 [ i - M , i - 1 ] 中,ans = pf [ i ] - pf [ j ] 的最大值,在遍历过程中,i为一个定值,ans 与 j 的取值有关,转化为求 pf [ j ] 的最小值,试了一下暴力枚举,果不其然TLE了。所以第二步进行单调队列优化,求一段区间内最小前缀和的下标。

有一个东西和你要的一个答案相关,然后这个答案要及时排除过时决策。

#include<bits/stdc++.h>
using namespace std;
#define qio ios::sync_with_stdio(0), cin.tie(0),cout.tie(0);
typedef long long ll;
const int N = 5e5 + 10;
int a[N];
ll pf[N];
int main() {
	qio
	int n, m;  cin >> n >> m;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++) pf[i] = pf[i - 1] + a[i];
	ll ans = -2e9;
	deque<int> dq;
	dq.push_back(0);//赋初值,防止出现第一个为最大的,之后单调递减的数据。
	//此时答案即为前M个数的和,但求最小值时没有考虑到下标为0的前缀和,因此出现问题
	for (int i = 1; i <= n; i++) {
		while (dq.size() && dq.front() < i - m) dq.pop_front();
		while (dq.size() && pf[dq.back()] >= pf[i]) dq.pop_back();
		dq.push_back(i);
		ans = max(ans, pf[i] - pf[dq.front()]);
	}
	cout << ans;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值