基本数据结构的应用一 栈和队列
单调栈 单调队列 和 优先队列的应用
1.单调栈
单调栈是指一个栈内部的元素是具有严格单调性的一种数据结构,分为单调递增栈和单调递减栈。
单调栈有两个性质
1.满足从栈顶到栈底的元素具有严格的单调性
2.满足栈的后进先出特性越靠近栈底的元素越早进栈
元素进栈过程
对于一个单调递增栈来说 若当前进栈的元素为 a 如果a < 栈顶元素则直接将a 进栈 如果 a >= 当前栈顶元素则不断将栈顶元素出栈知道满足 a < 栈顶元素
模拟一个数列构造一个单调递增栈
进栈元素分别为3,4,2,6,4,5,2,3。
首先 3 进栈 变为 {3};
然后 4 进栈 先让 3 出栈 再进栈 变为 {4};
2 进栈 变为 {4,2};
对于 6 先让 4,2 出栈 再让 6 进栈 {6};
4 进栈 {6,4};
让4 出栈 5进栈 {6,5};
然后2进 2出 3进 变为 {6,5,3};
由于单调栈只能对栈顶位置进行操作 所以一般应用于只有对数组有一边有限制的地方
实现用STL的栈实现即可 模拟栈也行
2.单调队列
单调队列是指一个队列内部的元素具有严格单调性的一种数据结构,分为单调递增队列和单调递减队列。
单调队列满足两个性质
1.单调队列必须满足从队头到队尾的严格单调性。
2.排在队列前面的比排在队列后面的要先进队。
元素进队列的过程对于单调递增队列,对于一个元素a 如果 a > 队尾元素 那么直接将a扔进队列 如果 a <= 队尾元素 则将队尾元素出队列 知道满足 a 大于队尾元素即可;
实现用 stl 的双端队列即可。
由于双端队列即可以在队头操作也可以在队尾操作那么这样的性质就弥补了单调栈只能在一遍操作的不足可以使得其左边也有一定的限制。
question 1:给你n个正数一个区间的权值定义为这个区间的所有数之和乘以这个区间内最小的数求所有区间中权值最大的区间。
solve : 对于这个题目我们不难想到要看每一个数作为最小值(或者最大值)的最长的区间是什么,对于这一类问题我们可以考虑用单调栈或者单调队列 用单调递减栈维护一下每次每个数的最大影响区间就是栈的前一个数到下一个要使得这个数出栈的位置。可以将栈中的每一个数看做一堵墙过了这个墙最小值就变成了墙上的这个数。
AC code :
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 1e5 + 10;
struct node{
long long num;
int pos;
};
node a[maxn];
long long sum[maxn] = {0};
int main(){
int n;
scanf("%d",&n);
int l = 1;
int r = n;
long long ans = 0;
for(int i = 1;i <= n; ++ i){
scanf("%lld",&a[i].num);
sum[i] = sum[i - 1] + a[i].num;
a[i].pos = i;
}
stack
for(int i = 1;i <= n; ++ i){
if(st.empty()){
st.push(a[i]);
continue;
}
while(!st.empty() && st.top().num >= a[i].num){
node u = st.top();
st.pop();
int j;
if(!st.empty())
j = st.top().pos;
else{
j = 0;
}
long long temp = (sum[i - 1] - sum[j]) * u.num;
if(temp > ans){
ans = temp;
l = j + 1;
r = i - 1;
}
// cout << temp << endl;
}
st.push(a[i]);
}
int rr = n,ll = 1;
while(!st.empty()){
node u = st.top();
st.pop();
if(st.empty()){
ll = 0;
}
else{
ll = st.top().pos;
}
long long temp = (sum[rr] - sum[ll]) * u.num;
if(temp > ans){
ans = temp;
l = ll + 1;
r = rr;
}
// cout << temp << endl;
}
printf("%lld\n%d %d\n",ans,l,r);
return 0;
}
question 2 : 求栅栏矩形的最大面积
这个问题和上面那个问题十分类似 我们也只需要一个单调栈 找到一个数最大的影响区间就可以了。还是用递减单调栈。
question 3 : POJ 3017
将一个由N个非负整数组成的序列划分成若干段,要求每段数字 的和不超过M,求每段的最大值的和最小的划分方法,输出这个 最小的和
solve : 对于这个问题我们不难想到一个状态为一维转移为o(n)的 1D/1D方程 但是 时间复杂度为 O(n ^ 2)的算法是时间复杂度不能接受的。
dp(i) 表示前i个划分成若干段的最大值的和最小。
dp[i] = min(dp[j] + maxnum{A[j + 1]...A[i]}) ;
对于这个方程我们不难发现 dp(i)是单调递增的 然后可以发现maxnum是从右向左单调递增的这个时候就可以使用一个单调队列进行维护
假设x为A[j + 1]...A[i]最大值的位置,即 • A[x] = maxnum{A[j + 1]...A[i]}
•则,对于任意y满足y >= j && y < x • maxnum{A[y + 1]...A[i]}为定值
所以我们就可以用单调队列加上 mutiset 将其优化到 nlogn级别 就可以ac了
AC code :
//#include
#include
#include
#include
#include
using namespace std;
const int maxn = 1e5 + 10;
long long a[maxn] = {0};
long long sum[maxn] = {0};
int n,m;
long long dp[maxn] = {0};
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= n; ++ i){
scanf("%lld", &a[i]);
sum[i] = sum[i-1] + a[i];
}
for(int i = 1;i <= n; ++ i)
dp[i] = 10000000000000007;
dp[0] = 0;
dp[1] = a[1];
long long mm = a[1];
deque
dq.push_back(1);
for(int i = 2;i <= n; ++ i){
mm = max(mm,a[i]);
while(!dq.empty() && a[i] >= a[dq.back()]){
dq.pop_back();
}
dq.push_back(i);
while(!dq.empty() && sum[i] - sum[dq.front() - 1] > m)
dq.pop_front();
int l = lower_bound(sum + 1,sum + n + 1, sum[i] - m) - sum;
if(sum[i] - sum[l-1] > m)
l ++;
int k = dq.size();
for(int j = 0;j < k; ++ j){
dp[i] = min(dp[i],dp[l - 1] + a[dq.front()]);
int u = dq.front();
l = u + 1;
dq.pop_front();
dq.push_back(u);
}
}
if(mm > m)
printf("-1\n");
else
printf("%lld\n",dp[n]);
return 0;
}