0x11 栈
栈是一种后进先出的线性数据结构
AcWing 41.包含min函数的栈
维护两个栈,一个记录栈的值,另一个单调栈,记录下当前的最小值即可
coding
AcWing 128. 编辑器
开两个栈维护,类似对顶堆的操作,我们把他叫做对顶栈好了
令\(P\)为光标位置,分别开两个栈\(a,b\)
栈\(a\)存\(P\)之前的数,栈\(b存\)P$之后的数
\(sum\)是前缀和,\(f\)是前缀和的最大值
对于操作\(L\),把\(x\)压入栈\(a\)并更新\(sum\)和\(f\)
对于操作\(D\) ,栈\(a\)栈顶弹出
对于操作\(L\),把栈顶\(a\)弹出并压入栈\(b\)
对于操作\(R\),把栈顶\(b\)弹出并压入栈\(a\)同时更新\(sum\)和\(f\)
对于操作\(Q\),返回\(f[x]\)
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5 , INF = 0x7ffffff;
int T , opt , a[N] , b[N] , sum[N] , f[N] , ta = 0 , tb = 0;
inline int read( bool _ )
{
register int x = 0 , f_ = 1;
register char ch = getchar();
if( _ )
{
while( ch < '0' || ch > '9' )
{
if( ch == '-' ) f_ = -1;
ch = getchar();
}
while( ch >= '0' && ch <= '9')
{
x = ( x << 3 ) + ( x << 1 ) + ch - '0';
ch = getchar();
}
return x * f_;
}
else
{
while( ch != 'L' && ch != 'R' && ch != 'I' && ch != 'D' && ch != 'Q' ) ch = getchar();
return int(ch);
}
}
inline void work_1()
{
a[ ++ ta ] = read(1);
sum[ta] = sum[ ta - 1 ] + a[ta];
f[ta] = max( sum[ta] , f[ ta - 1] );
return ;
}
inline void work_2()
{
if( ta > 0 ) ta --;
return ;
}
inline void work_3()
{
if( ta > 0 )b[ ++ tb] = a[ ta ] , ta --;
return ;
}
inline void work_4()
{
if( !tb ) return ;
a[ ++ ta ] = b[tb];
tb --;
sum[ta] = sum[ta - 1] + a[ta];
f[ta] = max( sum[ta] , f[ ta - 1] );
return ;
}
inline void work_5()
{
printf("%d\n",f[ read(1) ] );
return ;
}
int main()
{
f[0] = -INF;
T = read(1);
while( T -- )
{
opt = read(0);
if(opt == 'I' ) work_1();
else if(opt == 'D' ) work_2();
else if(opt == 'L' ) work_3();
else if(opt == 'R' ) work_4();
else work_5();
}
return 0;
}
AcWing 131. 直方图中最大的矩形
画图手玩样例就能发现规律
单调栈的经典应用,不过我比较懒,STL+O2直接水过去
#include <bits/stdc++.h>
#pragma GCC optimize(2)
#define LL long long
using namespace std;
const int N = 100005;
int n , now , width ;
LL res;
struct node
{
int w , h;
}_;
stack< node > s;
inline int read()
{
register int x = 0;
register char ch = getchar();
while( ch < '0' || ch > '9' ) ch = getchar();
while( ch >= '0' && ch <= '9' )
{
x = ( x << 3 ) + ( x << 1 ) + ch - '0';
ch = getchar();
}
return x;
}
inline node make( int x , int y )
{
_.h = x , _.w = y;
return _;
}
int main()
{
while( 1 )
{
n = read();
if( !n ) return 0;
res = 0;
for( register int i = 1; i <= n ; i ++ )
{
now = read();
if( s.empty() || now > s.top().h ) s.push( make( now , 1 ) );
else
{
width = 0;
while( !s.empty() && s.top().h > now )
{
width += s.top().w;
res = max( res , (LL)width * s.top().h );
s.pop();
}
s.push( make( now , width + 1 ) );
}
}
width = 0;
while( !s.empty() )
{
width += s.top().w;
res = max( res , (LL)width * s.top().h );
s.pop();
}
printf( "%lld\n" , res );
}
return 0;
}
0x12 队列
队列是一种“先进先出”的线性数据结构,手写队列时可以用循环队列来优化空间
队列还有一些变形体,优先队列,单调队列,双端队列,这些在\(STL\)中都是有的,不过常数比较大普通队列手写即可
另外优先队列在pbds中也有
AcWing 132. 小组队
这道题本身并不难,只是数据的处理比较恶心
首先开一个队列为维护小组,再开\(n\)个队列维护每个小组的成员
每次压入一个元素,就把这个元素加入这个小组的队列,如果这个小组的队列是空的就把他加入总的队列
每次弹出一个元素,就把总队列队头的小组弹出一个,如果队头小组的队列此时为空,就把队头小组从总队列总弹出
这道题并不是十分的卡常数,不开\(O2\)貌似能过,
另外插队不是好习惯,小心被打
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
const int N = 1e6 + 5 , M = 1005;
int n , t , m , num , cub[N];
string opt;
map< int , queue<int> > member;
queue< int > team;
inline int read()
{
register int x = 0;
register char ch = getchar();
while( ch < '0' || ch > '9' ) ch = getchar();
while( ch >= '0' && ch <= '9' )
{
x = ( x << 1 ) + ( x << 3 ) + ch - '0';
ch = getchar();
}
return x;
}
inline void push()
{
num = read();
if( member[ cub[num] ].empty() ) team.push( cub[num] );
member[ cub[num] ].push( num );
return ;
}
inline void pop()
{
num = team.front();
printf( "%d\n" , member[ num ].front() );
member[ num ].pop();
if( member[ num ].empty() ) team.pop();
}
inline void work( int k )
{
n = read();
if( !n ) exit(0);
printf( "Scenario #%d\n" , k );
while( !team.empty() )
{
num = team.front();
while( !member[ num ].empty() ) member[ num ].pop();
team.pop();
}
memset( cub , 0 , sizeof(cub) );
for( register int i = 1 ; i <= n ; i ++ )
{
t = read();
while( t -- ) cub[ read() ] = i;
}
while( 1 )
{
cin >> opt;
if( opt == "ENQUEUE" ) push();
else if( opt == "DEQUEUE" ) pop();
else break;
}
puts("");
return ;
}
int main()
{
for( register int k = 1 ; 1 ; k ++ ) work(k);
return 0;
}
AcWing 135. 最大子序和
单调队列的基操
首先对于区间和的问题一般情况下都是转发乘前缀和数组,做差即可
然后就是找左右端点的问题
令前缀和数组为\(s\)
已经枚举的右端点\(i\)和当前的左端点\(j\)
此时再任意一个\(k\)如果满足\(k<j<i\)且\(s[k]>s[j]\),着\(k\)无论如何也不可能成为最有解,因为对于任意的\(i\)如果可以选\(j\)则\(j\)一定\(k\)更优
所以我们发现需要维护一个单调递增的序列,并且随着\(i\)的有移,将会有部分的\(j\)不能使用
符合单调队列的性质所以用单调队列来维护,队列储存的元素是前缀和数组的下标,队头为\(l\),队尾为\(r\)
对于每次枚举的\(i\)有以下几个操作
- 如果\(q[l] < i - m\)将队头出对
- 此时的\(l\)就是最有的\(j\)更新答案
- 维护单调队列性质并把\(i\)放入队列
#include <bits/stdc++.h>
using namespace std;
const int N = 300000;
int n , m , s[N] , q[N] , l = 1 , r = 1 , res ;
inline int read()
{
register int x = 0 , f = 1;
register char ch = getchar();
while( ch < '0' || ch > '9' )
{
if( ch == '-' ) f = -1;
ch = getchar();
}
while( ch >= '0' && ch <= '9' )
{
x = ( x << 3 ) + ( x << 1 ) + ch - '0';
ch = getchar();
}
return x * f;
}
int main()
{
n = read() , m = read();
for( register int i = 1 ; i <= n ; i ++ ) s[i] = s[i-1] + read();
for( register int i = 1 ; i <= n ; i ++ )
{
while( l <= r && q[l] < i - m ) l ++;
res = max( res , s[i] - s[ q[l] ] );
while( l <= r && s[ q[r] ] >= s[i] ) r --;
q[ ++ r ] = i;
}
cout << res << endl;
return 0;
}