给定一个序列 a a a ,对于 a a a 中的每个数找到在他左(右)边,最近的比他大(小)的数是什么。
例:对于 a a a 中的每个数找到他左边最近的比他小的数是什么,没有输出 -1
5
3 4 2 7 5
ans = -1 3 -1 2 2
栈内一开始放入负无穷,对每个 a [ i ] a[i] a[i] 和栈顶元素判断就行,如果 a [ i ] a[i] a[i] 更小,那么栈顶元素就不是比 $a[i] $ 小的元素,不断弹栈,直到不能弹完为止。
解释单调栈为什么是单调的。
指针遍历到 i i i ,有下标 x < y x
如果要求右边,就从n到1循环。
求最小,就是 a [ i ] < = 栈顶元素 a[i] <= 栈顶元素 a[i]<=栈顶元素 ,哨兵置 -0x3f3f3f3f。
还有一种记忆方法:
stk[++tt] = -0x3f3f3f3f;
fo(i,1,n){
// 一直有-无穷
while(a[i] <= stk[tt]) // 如果是<=,栈内元素会是严格单调增的
tt--;
if(stk[tt] == -0x3f3f3f3f)
cout<<"-1 ";
else
cout<<stk[tt]<<" ";
stk[++tt]=a[i];
}
int a[N],stk[N],tt;
void solve(){
cin>>n;
fo(i,1,n){
cin>>a[i];
}
stk[++tt] = -0x3f3f3f3f;
fo(i,1,n){
// 一直有-无穷
while(a[i] <= stk[tt])
tt--;
if(stk[tt] == -0x3f3f3f3f)
cout<<"-1 ";
else
cout<<stk[tt]<<" ";
stk[++tt]=a[i];
}
}
y总是用栈空不空判断的
int a[N],stk[N],tt;
void solve(){
cin>>n;
fo(i,1,n){
cin>>a[i];
}
fo(i,1,n){
// 一直有-无穷
while(tt && a[i] <= stk[tt])
tt--;
if(tt)
cout<<stk[tt]<<" ";
else
cout<<"-1 ";
stk[++tt]=a[i];
}
}
建议以后都写 加哨兵的写法,不用判断边界
ll pre[N],suf[N];
void solve(){
while(cin>>n,n){
tt=0;
ll ans=0;
fo(i,1,n)cin>>a[i];
h[0] = h[n+1] = -1;
stk[0] = 0;
fo(i,1,n){
while(a[i] <= a[stk[tt]]){
tt --;
}
pre[i] = stk[tt];
stk[++tt] = i;
}
tt=0;
stk[0] = n+1;
for(int i=n;i>=1;i--){
while(a[i] <= a[stk[tt]]){
tt -- ;
}
suf[i]=stk[tt];
stk[++tt] = i;
}
for(int i=1;i<=n;i++){
cout<<i<<" "<<pre[i]<<" "<<suf[i]<<endl;
ans = max(ans,(suf[i]-1-(pre[i]+1)+1)*a[i]);
}
cout<<ans<<endl;
}
}
总结:单调栈和单调队列都可以叫 双端队列 , 单调栈可以存值存下标。单调队列为了判断队头何时出队,存数组下标。
N遍直方图中的最大矩形,注意单调栈写带哨兵的
对于每一行,先预处理出up[]来,然后做n遍单调栈。
int n,m;
char a[1100][1100];
int up[1100][1100];
int stk[1100],tt;
int ans;
void work(int x[1100]){
int l[1100],r[1100];
// 左边第一个比他小的数,带哨兵的
x[0] = x[m+1] = -1;
stk[++tt] = 0;
for(int i=1;i<=m;i++){
while(x[i] <= x[stk[tt]])
tt--;
l[i] = stk[tt]; // 下标
stk[++tt]=i;
}
tt = 0;
stk[++tt] = m+1;
for(int i=m;i>=1;i--){
while(x[i] <= x[stk[tt]])
tt--;
r[i] = stk[tt];
stk[++tt]=i;
}
for(int i=1;i<=m;i++){
ans = max(ans,(r[i]-1-(l[i]+1)+1) * x[i]);
}
}
void solve(){
cin>>n>>m;
fo(i,1,n)fo(j,1,m)cin>>a[i][j];
fo(i,1,n){
fo(j,1,m){
if(a[i][j] == 'R') up[i][j] = 0;
else{
up[i][j] = up[i-1][j]+1;
}
}
}
// 做n遍直方图中的最大矩形。
fo(i,1,n){
work(up[i]);
}
cout<<ans*3<<endl;
}
int main(){
solve();
return 0;
}
单调栈综合应用。
主要还是思维。
注意求 R时,单调栈中的指针是比i指针靠右的,就像求L时,单调栈中的指针比i指针靠左一样!
const int N=1e6+10,M=1e9+7;
ll n,a[N],l[N],r[N];
ll ans[N];
int stk[N],tt;
void solve(){
cin>>n;
fo(i,1,n)cin>>a[i];
// 求每个元素第一个比他小的元素
tt = 0;
a[0] = -1;
stk[++tt] = 0;
for(int i=1;i<=n;i++){
while(a[i] <= a[stk[tt]])
tt--;
int left = stk[tt];
l[i] = l[left] + (ll)(i-left)*a[i];
stk[++tt] = i;
}
a[n+1] = -1;
tt = 0;
stk[0] = n+1;
for(int i=n;i>=1;i--){
while(a[i] <= a[stk[tt]])
tt--;
int left = stk[tt];
// cout<
r[i] = r[left] + (ll)(left-i)*a[i];
stk[++tt] = i;
}
ll res = 0,k;
for(int i=1;i<=n;i++){
if(l[i] + r[i] - a[i] > res){
res = l[i] + r[i] - a[i];
k = i;
}
}
ans[k] = a[k];
for(int i=k-1;i>=1;i--){
ans[i] = min(a[i],ans[i+1]);
}
for(int i=k+1;i<=n;i++){
ans[i] = min(a[i],ans[i-1]);
}
for(int i=1;i<=n;i++){
cout<<ans[i]<<' ';
}
}
int main(){
solve();
return 0;
}
和求最大矩形面积类似,但是我搞不懂怎么算的方案数(真看不懂)
学会了另一种计算图形中长方形的方法,确定两条边,组合数学乘法原理就行。
typedef long long ll;
int n,m,a[1100][1100];
int l[1100],r[1100]; // l表示左边第一个不大于 , r表示右边第一个小于 ans = (i-l)*(r-i)*h
int up[1100][1100];
int stk[N],tt;
ll ans;
void work(int x[]){
tt = 0;
x[0] = -1;
stk[++tt] = 0;
fo(j,1,m){
while(x[j] < x[stk[tt]])
tt--;
l[j] = stk[tt];
stk[++tt] = j;
}
tt = 0;
x[m+1] = -1;
stk[++tt] = m+1;
for(int j=m;j>=1;j--){
while(x[j] <= x[stk[tt]])
tt--;
r[j] = stk[tt];
stk[++tt] = j;
}
// fo(i,1,m)cout<
// cout<
fo(i,1,m){
ans += (i-l[i]) * (r[i] - i) * (x[i]);
}
}
void solve(){
cin>>n>>m;fo(i,1,n)fo(j,1,m){char s;cin>>s;if(s=='.')a[i][j]=1;else a[i][j]=0;}
fo(i,1,n){
fo(j,1,m){
if(a[i][j]){
up[i][j] = up[i-1][j]+1;
}
else up[i][j] = 0;
}
}
fo(i,1,n){
work(up[i]);
}
cout<<ans;
}
int main(){
solve();
return 0;
}
排序后发现,当前数会被后边的数删掉,并且当前数的存在不影响后边的数。
对于 i i i 找 1 ≤ a [ j ] − a [ i ] ≤ k 1 \le a[j]-a[i] \le k 1≤a[j]−a[i]≤k 的 j j j , 可以找最小的 a [ j ] > a [ i ] + k a[j] > a[i]+k a[j]>a[i]+k , 判断 a [ j − 1 ] − a [ i ] a[j-1] - a[i] a[j−1]−a[i] 是否大于1。
法一:upper_bound。
int n,k,a[N];
void solve(){
cin>>n>>k;
fo(i,1,n)cin>>a[i];
sort(a+1,a+n+1);
int ans=0;
for(int i=1;i<=n;i++){
int j = upper_bound(a+1,a+n+1,a[i]+k)-a;
if(a[j-1] - a[i] >=1) ans ++;
}
cout<<n-ans;
}
法二:双指针。
原来双指针while中 j 的限制可以绝对 i和j哪个是尾指针,哪个是头指针
证明正确性:
一般双指针都是找4个指针,然后找一个矛盾。
枚举到 i i i ,有 j j j 满足 a [ j ] > a [ i ] + k , a [ j ] 最小 a[j] > a[i] +k,a[j]最小 a[j]>a[i]+k,a[j]最小 。 i i i 向右移动到了 i ′ i' i′ ,假设有 j ′ < j j' < j j′<j ,和 i ′ i' i′ 配对。
发现 a [ j − 1 ] − a [ i ] ≤ k a[j-1]-a[i] \le k a[j−1]−a[i]≤k 有, a [ j − 1 ] − a [ i ′ ] ≤ k a[j-1]-a[i'] \le k a[j−1]−a[i′]≤k , 所以 i ′ i' i′ 对应的 J J J 最小也是 j j j 不是 j ′ j' j′ ,矛盾,说明, i i i 向右过程中 , j j j 也是单调向右的。
int n,k,a[N];
void solve(){
cin>>n>>k;
fo(i,1,n)cin>>a[i];
sort(a+1,a+n+1);
// 42 53 54 55 101 101 102
int ans=0;
for(int i=1,j=1;i<=n;i++){
while(j<=n && a[j] - a[i] <=k){
j++;
}
if(a[j-1] - a[i] >=1) ans ++;
}
cout<<n-ans;
}
给定一个 N × M N×M N×M 的矩阵 A A A,请你统计有多少个子矩阵 (最小 1 × 1 1×1 1×1,最大 N × M N×M N×M) 满足子矩阵中所有数的和不超过给定的整数 K K K ?
对于 100 100% 100 的数据, 1 ≤ N , M ≤ 500 ; 0 ≤ A i j ≤ 1000 ; 1 ≤ K ≤ 250000000 1≤N,M≤500;0≤A_{ij}≤1000;1≤K≤250000000 1≤N,M≤500;0≤Aij≤1000;1≤K≤250000000 。
二维数组一维化的TRICK题。
矩阵由左上角和右下角确定,一共四个自由度。枚举矩阵上下边界,确定 x 1 , x 2 x1,x2 x1,x2 还剩两个自由度。
问题转化为,一个 一维数组 有多少段连续元素和不超过 K K K 。
双指针分析:
双指针考虑 [ j , i ] [j,i] [j,i] ,定义 j = f [ i ] j = f[i] j=f[i] , j j j 表示 以 i i i 为右端点的距离 i i i 最远的满足条件的 j j j 。
到这我竟然还不会计数 a n s + = i − j + 1 ans += i-j+1 ans+=i−j+1 。
如果 i i i 固定, j j j 单调向左/右移动,双指针成立,即可将两个自由度优化成一个。
显然成立,反证法 [ j , i ] [j,i] [j,i] 同时向右移动。
/*
A: 10min
B: 20min
C: 30min
D: 40min
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define pb push_back
#define all(x) (x).begin(),(x).end()
#define mem(f, x) memset(f,x,sizeof(f))
#define fo(i,a,n) for(int i=(a);i<=(n);++i)
#define fo_(i,a,n) for(int i=(a);i<(n);++i)
#define debug(x) cout<<#x<<":"<<x<<endl;
#define endl '\n'
using namespace std;
//#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math,O3")
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
template<typename T>
ostream& operator<<(ostream& os,const vector<T>&v){for(int i=0,j=0;i<v.size();i++,j++)if(j>=5){j=0;puts("");}else os<<v[i]<<" ";return os;}
template<typename T>
ostream& operator<<(ostream& os,const set<T>&v){for(auto c:v)os<<c<<" ";return os;}
template<typename T1,typename T2>
ostream& operator<<(ostream& os,const map<T1,T2>&v){for(auto c:v)os<<c.first<<" "<<c.second<<endl;return os;}
template<typename T>inline void rd(T &a) {
char c = getchar(); T x = 0, f = 1; while (!isdigit(c)) {if (c == '-')f = -1; c = getchar();}
while (isdigit(c)) {x = (x << 1) + (x << 3) + c - '0'; c = getchar();} a = f * x;
}
typedef pair<long long ,long long >PII;
typedef pair<long,long>PLL;
typedef long long ll;
typedef unsigned long long ull;
const int N=1e6+10,M=1e9+7;
int n,m,k;
int a[510][510],sum[510][510];
void solve(){
cin>>n>>m>>k;
fo(i,1,n)fo(j,1,m)cin>>a[i][j];
fo(j,1,m){
fo(i,1,n){
sum[i][j] = sum[i-1][j] + a[i][j];
}
}
ll ans = 0;
fo(x1,1,n){
fo(x2,x1,n){
int S = 0;
for(int i=1,j=1;i<=m;i++){
S += sum[x2][i] - sum[x1-1][i];
while(j<=i && S > k){ // j<=i 表示 [j,i] 只有一个元素也可能合法
S-=sum[x2][j] - sum[x1-1][j];
j++;
}
if(S<=k){
ans +=(i-j+1);
}
}
}
}
cout<<ans<<endl;
}
int main(){
solve();
return 0;
}