就是一些很神奇的数据结构
A:最大矩形
题目:
给一个直方图,求直方图中的最大矩形的面积。例如,下面这个图片中直方图的高度从左到右分别是2, 1, 4, 5, 1, 3, 3, 他们的宽都是1,其中最大的矩形是阴影部分。
input:
输入包含多组数据。每组数据用一个整数n来表示直方图中小矩形的个数,你可以假定1 <= n <= 100000. 然后接下来n个整数h1, …, hn, 满足 0 <= hi <= 1000000000. 这些数字表示直方图中从左到右每个小矩形的高度,每个小矩形的宽度为1。 测试数据以0结尾。
output:
对于每组测试数据输出一行一个整数表示答案。
在矩形条里面选取最大的矩形,首先想到暴力做法的话,估计必须会有O(n^2)的复杂度(每个点都找左右延申最远) 这样耗费不可接受。所以就要用到这里的数据结构,单调栈
所谓单调栈就是,以递增栈为例,如果加入元素小于栈顶,入栈,否则栈顶出栈,直到满足元素小于栈顶或者栈空。
对于矩形柱,从当前向右,要使得必须大于当前的矩形柱才可以延申。因此维护的是一个递增栈,是当前元素i被pop 的时候就说明这时候要加入的这个元素比当前元素小,也就是能够向右拓展的最大距离了。
向左拓展的距离类似,反向建立一个单增栈即可。
(说起来简单其实需要自己稍微模拟一下)
#include<iostream>
#include<stack>
using namespace std;
const int N = 100000 + 50;
long long maxit(long long a, long long b)
{
if (a > b)
{
return a;
}
else
{
return b;
}
}
int lf, rt;
int hight;
long long ans;
long long n, a[N], L[N], R[N], st[N];
int main()
{
cin >> n;
while(n!=0)
{
ans=0;
stack<long long> A;
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
int i = 0;
while (i < n)
{
if (A.empty() || a[A.top()] <= a[i])
{
A.push(i);
i++;
}
else
{
int tp = A.top();
A.pop();
long long area;
if (A.empty())
{
area = a[tp] * i;
ans = maxit(ans, area);
}
else
{
area = a[tp] * (i -1- A.top());
ans = maxit(ans, area);
}
}
}
while (!A.empty())
{
long long area;
int tp = A.top();
A.pop();
if (A.empty())
{
area = a[tp] * i;
ans = maxit(ans, area);
}
else
{
area = a[tp] * (i -1-A.top());
ans = maxit(ans, area);
}
}
cout << ans << endl;
cin>>n;
//for(int i=0)
}
return 0;
}
直接用了stl,据说手动模拟更简单?
B:TT’s Magic Cat
题目:
给定一个数组,在其(l,r)区间内的每个值都增加k,求q轮后的数组。
input:
第一行包含两个整数n,q(1≤n,q≤2⋅105)分别表示数组的长度和操作的轮数
第二行包含序列a的元素:整数a1、a2、…、an(-106≤ai≤106)。
接下来是q行,每一行代表一个操作。第i行包含用于第i操作的三个整数l、r和c(1≤l≤r≤n,-105≤c≤105)。
output:
打印出变化后的数组
暴力:O(n^2)的时间复杂度无法接受。
所以要做的就是用新的办法。我们用的是差分法》 那么啥是差分
(这样/我知道手写不是一个好习惯orz
#include<iostream>
using namespace std;
long long a[300020];
long long b[300020];
int main()
{
int m,n;
cin>>n>>m;
int lf,rt;
for(int i=0;i<n;i++)
{
scanf("%lld",&a[i]);
}
b[0]=a[0];
for(int i=1;i<n;i++)
{
b[i]=a[i]-a[i-1];
}
for(int i=0;i<m;i++)
{
int value;
scanf("%d%d%d",&lf,&rt,&value);
b[lf-1]=b[lf-1]+value;
b[rt]=b[rt]-value;
}
a[0]=b[0];
for(int i=1;i<n;i++)
{
a[i]=b[i]+a[i-1];
}
for(int i=0;i<n;i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
return 0;
}
C:平衡字符串
题目:
一个长度为 n 的字符串 s,其中仅包含 ‘Q’, ‘W’, ‘E’, ‘R’ 四种字符。
如果四种字符在字符串中出现次数均为 n/4,则其为一个平衡字符串。
现可以将 s 中连续的一段子串替换成相同长度的只包含那四个字符的任意字符串,使其变为一个平衡字符串,问替换子串的最小长度?
如果 s 已经平衡则输出0。
input:
一行字符表示给定的字符串s
output:
一个整数表示答案
样例输入:
QWER
样例输出:
0
样例输入:
QQWE
样例输出:
1
样例输入:
QQQE
样例输出:
2
样例输入:
QQQQ
样例输出:
3
这题要介绍的办法是,尺取法。简单说就是拿着一个游标尺子去取长度。
不符合条件,右标右移,符合条件,左标右移。当然尺取的区间需要连续。
显然这道题符合这样的条件。。那么剩下的就是进行统计了,统计区间外面的字母数量(首先每个字母该有几个我们是知道的) 然后……区间内部的字母都可以作为自由的去填补这些缺口,看看缺口能不能被补上确定是不是“符合条件”
#include<iostream>
#include<string>
#include<cstring>
#include<string.h>
using namespace std;
int a[4];
int ziyouji=0;
int bl(char c)
{
if(c=='Q') return 0;
else if(c=='W') return 1;
else if(c=='E') return 2;
else if(c=='R') return 3;
}
int minit(int a,int b)
{
if(a<b) return a;
else return b;
}
bool queren(int ll)
{
int tx=0;
for(int i=0;i<4;i++)
{
if(a[i]>ll)
{
return false;
}
tx+=ll-a[i];
}
if(tx==ziyouji)
{
return 1;
}
else
{
return 0;
}
}
int main()
{
int now;
memset(a,0,sizeof(a));
string x;
cin>>x;
int ans=x.length();
int t=x.length();
for(int i=0;i<t;i++)
{
if(x[i]=='Q') a[0]++;
else if(x[i]=='W') a[1]++;
else if(x[i]=='E') a[2]++;
else if(x[i]=='R') a[3]++;
}
int tl=t/4;
int l=0,r=0;
while(r<x.length())
{
if(queren(tl))
{
//cout<<"hello"<<l<<" "<<r<<endl;
ans=minit(ans,r-l);
a[bl(x[l])]++;
l++;
ziyouji--;
}
else
{
//cout<<"hi"<<l<<" "<<r<<endl;
a[bl(x[r])]--;
ziyouji++;
r++;
}
}
cout<<ans<<endl;
return 0;
}
D:滑动窗口
题目:
ZJM 有一个长度为 n 的数列和一个大小为 k 的窗口, 窗口可以在数列上来回移动. 现在 ZJM 想知道在窗口从左往右滑的时候,每次窗口内数的最大值和最小值分别是多少. 例如:
数列是 [1 3 -1 -3 5 3 6 7], 其中 k 等于 3.
input:
输入有两行。第一行两个整数n和k分别表示数列的长度和滑动窗口的大小,1<=k<=n<=1000000。第二行有n个整数表示ZJM的数列。
output:
输出有两行。第一行输出滑动窗口在从左到右的每个位置时,滑动窗口中的最小值。第二行是最大值。
样例输入:
8 3
1 3 -1 -3 5 3 6 7
样例输出:
-1 -3 -3 -3 3 3
3 3 5 5 6 7
涉及到和第一题类似的数据结构,单调队列。但和单调栈不同的是单调队列是双向的。
找局部的最大值和最小值,
1.首先维护双端队列的单调性,(假设从队头到队尾递减)当前元素若比队尾元素值小,则弹出元素直到满足条件。
2.同时要维护窗口内的元素个数,如果队首的元素已经在窗口外,就把他弹出。
3.每一次循环的队首元素就是当前窗口的最大值或者是最小值。
(同样不太好理解QAQ)
#include<iostream>
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<algorithm>
#include<deque>
#define MAXN 1000010
int a[MAXN];
int maxx[MAXN],minn[MAXN];
using namespace std;
int n,k;
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
deque<int> q;//储存下标
for(int i=1;i<=k-1;i++)
{ while(!q.empty()&&a[q.back()]>a[i])
q.pop_back();
q.push_back(i);
}
for(int i=k;i<=n;i++)//窗口从k开始向右移动 维护一个单增队列
{
while(!q.empty()&&a[q.back()]>a[i]) q.pop_back();//先维护单调性
q.push_back(i);
while(!q.empty()&&(i-q.front())>=k) q.pop_front();//然后维护窗口的大小
minn[i]=q.front();
}
q.clear();
for(int i=1;i<=k-1;i++)
{ while(!q.empty()&&a[q.back()]<a[i])
q.pop_back();
q.push_back(i);
}
for(int i=k;i<=n;i++)//窗口从k开始向右移动 维护一个单增队列
{
while(!q.empty()&&a[q.back()]<a[i]) q.pop_back();//先维护单调性
q.push_back(i);
while(!q.empty()&&(i-q.front())>=k) q.pop_front();//然后维护窗口的大小
maxx[i]=q.front();
}
for(int i=k;i<=n;i++) printf("%d ",a[minn[i]]);
cout<<endl;
for(int i=k;i<=n;i++) printf("%d ",a[maxx[i]]);
cout<<endl;
system("pause");
return 0;
}