一、序
周日就要打新生赛了,但是本蒟蒻越来越菜了怎么办,一天天的也不知道在忙什么,噢肯定不是因为天天在积你的破分(为什么留了一半的积分要把剩下那一半的积分也积了呢?!!)。积分积的我想死,低等人为什么要学高等数学啊。所以咱还是来敲点代码吧,结果发现啥啥都不会,别太抽象啊,我会破防的。
单调栈与单调队列维护了内部元素的单调性,多用while语句进行处理,以维护单调性。可在发现单调性后用于优化多重背包,将优化至
。下面几个题核心思想皆为用一个更优的元素代替之前的元素,不要的元素就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;
}