单调栈队列,前缀和与差分,尺取法

单调栈

最大矩形

  • 题目
    给一个直方图,求直方图中的最大矩形的面积。例如,下面这个图片中直方图的高度从左到右分别是2, 1, 4, 5, 1, 3, 3, 他们的宽都是1,其中最大的矩形是阴影部分。
    在这里插入图片描述
  • 输入输出
    输入包含多组数据。每组数据用一个整数n来表示直方图中小矩形的个数,你可以假定1 <= n <= 100000. 然后接下来n个整数h1, …, hn, 满足 0 <= hi <= 1000000000. 这些数字表示直方图中从左到右每个小矩形的高度,每个小矩形的宽度为1。 测试数据以0结尾。
    对于每组测试数据输出一行一个整数表示答案。

解题


  • 对于每一个单个矩形求出一个以它本身为高的最大矩形,而它的宽可以向两边扩展,扩展出的范围是第一个高小于它自身的单个矩形。

  • 单调栈
    为了找出向左扩展的最大范围,我们维护一个单调非递减的栈。当要往栈里压入新元素时,判断当前栈顶元素是否小于等于新元素;是,则入栈,否,则弹出栈顶元素,直到栈顶元素小于等于新元素或栈为空。
    同理向右扩展最大范围,只需要将元素插入顺序反过来再次计算范围。

  • 证明
    在这个单调栈内,新入栈元素底下的元素一定是它的范围边界(如果没有元素,边界为0)。第一,它底下的比它小(维护单调性);第二,被踢掉的一定比它大。

代码

#include<iostream>
#include<algorithm>

using namespace std;
typedef long long ll;

const int N = 1e5 + 5;

int n;
int a[N], s[N], l[N], r[N];

void fun()
{
    int t=0;
    a[0] = -1;
    for(int i = 1; i <= n; i ++ ){
        while(a[s[t]] >= a[i]) t--;
        l[i] = s[t];
        s[++t] = i;
    }
    reverse(a + 1, a + n + 1);
    a[0] = -1;t=0;
    for(int i = 1; i <= n; i ++ ){
        while(a[s[t]] >= a[i]) t--;
        r[i] = s[t];
        s[++t] = i;
    }

    ll res = 0;
    for(int i = 1, j = n; i <= n; i ++, j -- )
        res = max(res,(ll) a[i] * (n + 1 - l[j] - r[i] - 1));
    cout << res << endl;
}

int main()
{
	cin >> n;
    while(n){
        for(int i = 1; i <= n; i ++ )
            cin >> a[i];
    	fun();
    	cin >> n;
    }
    return 0;
}

前缀和与差分

  • 问题
    One day, the magic cat decided to investigate TT’s ability by giving a problem to him. That is select nn cities from the world map, and a[i]a[i] represents the asset value owned by the ii-th city.Then the magic cat will perform several operations. Each turn is to choose the city in the interval [l,r][l,r] and increase their asset value by cc. And finally, it is required to give the asset value of each city after qq operations.
  • 输入输出
    The first line contains two integers n,qn,q (1≤n,q≤2⋅105)(1≤n,q≤2·105) — the number of cities and operations.The second line contains elements of the sequence aa: integer。。。。。、

题目省略了。。。
大概意思是,有一个数组,我们想要对它进行q次操作,每次操作给定一个区间(u,v),这个区间内的所有元素同时+x,计算最后的数组。

解题

如果每次操作对区间内所有元素+x,需要大量的计算。

  • 差分
    重新构造数组,a[ i ] = a[ i ] - a[ i - 1],相对于原数组计算出前 i 个数和就是原数(前缀和)。这就是说现在再改变第 i 个数会影响 i 后面的所有数。
    区间(u,v)+x相当于a[ u ] + x和a[ v + 1] - x。

代码

#include<iostream>

using namespace std;

int main(){
	int  *a,n,q,l,r,o;
	cin>>n>>q;
	a=new int[n+10];
	a[0]=0;a[n+1]=0;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	for(int i=n;i>0;i--){
		a[i]-=a[i-1];
	}
	for(int i=0;i<q;i++){
		cin>>l>>r>>o;
		a[l]+=o;
		a[r+1]-=o;
	}
	a[0]=0;
	long long s=0;
	for(int i=1;i<=n;i++){
		s=(long long) (a[i]+s);
		cout<<s<<" ";
	}
} 

尺取法

平衡字符串

  • 问题
    一个长度为 n 的字符串 s,其中仅包含 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符。如果四种字符在字符串中出现次数均为 n/4,则其为一个平衡字符串。现可以将 s 中连续的一段子串替换成相同长度的只包含那四个字符的任意字符串,使其变为一个平衡字符串,问替换子串的最小长度?如果 s 已经平衡则输出0。

  • 输入输出
    一行字符表示给定的字符串s,一个整数表示答案

解题

  • 连续范围
    最终目的是一段连续的最短范围,想象的个尺子(长度任意变化)定义为(l,r),当r-l最小时就是需要更改的最小连续范围。

  • 最小范围
    如何找到这个最小的范围。
    首先要知道什么样的字符串是不合格的——有一个字符数不等于n/4。也就是说如果不合格,一定有一个大于n/4。需要修改的就是这个大于n/4的字符。
    假设一个尺子向后不定长移动。我们用这个尺覆盖一定范围(从原字符中移除),如果剩余的仍然有一个大于n/4,说明覆盖范围不够,r向右移动。如果都小于n/4,可以尝试l右移。
    覆盖范围内如何修改并不重要,只需要知道覆盖当前区间,用其它字符填充有可能构成平衡字符串即可。

代码

#include<iostream>
#include<string>
#include<map>
#include<algorithm>

using namespace std;

int n;
string s;
map<char,int> m;

int main(){
	cin>>s;
	n=s.length();
	for(int i=0;i<n;i++){
		m[s[i]]++;
	}
	int l=-1,r,nn=n/4,mins=n;
	for(r=-1;r<n;r++){
		m[s[r]]--;
		while(m['Q']<=nn&&m['W']<=nn&&m['E']<=nn&&m['R']<=nn&&l<=r){
			mins=min(mins,r-l+1);
			m[s[l++]]++; 
		}
	} 
	cout<<mins; 
} 

单调队列

滑动窗口

  • 问题
    ZJM 有一个长度为 n 的数列和一个大小为 k 的窗口, 窗口可以在数列上来回移动. 现在 ZJM 想知道在窗口从左往右滑的时候,每次窗口内数的最大值和最小值分别是多少. 例如:
    数列是 [1 3 -1 -3 5 3 6 7], 其中 k 等于 3.
  • 输入输出
    输入有两行。第一行两个整数n和k分别表示数列的长度和滑动窗口的大小,1<=k<=n<=1000000。第二行有n个整数表示ZJM的数列。输出有两行。第一行输出滑动窗口在从左到右的每个位置时,滑动窗口中的最小值。第二行是最大值。

解题

  • 单调
    维护队列的单调性就能找到一定范围内最大(小)值。

  • 维护窗口
    另外窗口大小也要考虑,每次键入新元素之前一定要检查队首元素是否已经不再窗口范围内。
    其它部分与单调栈相似。
    可以同时维护两个队列,一次性找出最大值和最小值。
    也可以分开维护,分开计算最值

代码

#include<stdio.h>
using namespace std;
const int N=10e6+1;
int a[N],q1[N],q2[N];
int main(){
	int n,k;
	scanf("%d%d",&n,&k);
	int f,t;
	for(int i=0;i<n;i++){
		scanf("%d",a+i);
	}
	f = 0, t = -1;
    for(int i=0; i<n; i++ ){
        if(f<=t&&q1[f]<i-k+1)
			 f++;
        while(f<=t&&a[q1[t]]>=a[i])
			 t--;
        q1[++t]=i;
        if(i>=k-1) 
			printf("%d ",a[q1[f]]);
    }
    printf("\n");
    f = 0, t = -1;
    for(int i=)
    for(int i=0; i<n; i++ ){
        if(f<=t&&q2[f]<i-k+1)
			 f++;
        while(f<=t&&a[q2[t]]<=a[i])
			 t--;
        q2[++t]=i;
        if(i>=k-1) 
			printf("%d ",a[q2[f]]);
    }
    printf("\n");
    return 0;
}
发布了7 篇原创文章 · 获赞 2 · 访问量 200

猜你喜欢

转载自blog.csdn.net/Schrodingerofcat/article/details/105149299