菜需捆的 单调栈 刷题记录
单调栈的一些性质(单增为例)
- 在栈里的元素一定比后面的都要小(当前走到的)
- 可以访问到第一个小于我的元素下标(就是在栈里第一个小于我的那个元素)
- 如果我在栈里是第一个元素,说明我比前面的元素都小(考虑最小的那个元素)
BZOJ1113: [Poi2008]海报PLA
- 单调栈扫一遍就是了,给出的矩形的长没有什么用,只看矩形的宽,出栈的时候ans++,最后统计栈里元素个数,加到ans里
- 注意的地方:当栈里最大元素跟当前相同时不进栈也不加ans
- 代码:
#include <bits/stdc++.h> #define nmax 250010 using namespace std; typedef long long ll; int n,idx=0; ll ans=0; ll s[nmax]; //单调递增的栈 int main(){ cin>>n; ll a,b; scanf("%lld%lld",&a,&b); s[0]=b; for (int i=1; i<n; i++) { scanf("%lld%lld",&a,&b); //把b入栈 while(s[idx]>b){ ans++; idx--; } if(s[idx]!=b) s[++idx]=b; } //统计栈内不同的元素个数 for (int i=idx; i>0; i--){ if(s[i]!=s[i-1]) ans++; } ans++; cout<<ans<<endl; return 0; }
1012: [JSOI2008]最大数maxnumber
- 题意:要支持两种操作,1.末尾插入一个数,2.查询当前数列中末尾L个数中的最大的数,并输出这个数的值
- 只需要维护一个单调下降的栈就可以了,因为后面的数比我还大的话我在所有的查询里都不会再有贡献了。
- 注意还有一个前缀和和二分查找的小技巧
- 代码:
1 #include <bits/stdc++.h> 2 #define nmax 200005 3 4 using namespace std; 5 typedef long long ll; 6 int op,is=0; 7 ll mod,x,tmp=0,l=0; 8 char in[10]; 9 ll s[nmax],num[nmax]={0}; 10 11 int main(){ 12 scanf("%ld%lld",&op,&mod); 13 for (int i=0; i<op; i++) { 14 scanf("%s%lld",in,&x); 15 if( in[0]=='A' ){ 16 l++; 17 x=(x+tmp)%mod; 18 int ta=0; 19 while( s[is]<=x && is>=0 ) { 20 s[is]=x; 21 ta+=( num[is]-num[is-1] ); 22 is--; 23 } 24 s[++is]=x; 25 num[is]=ta+num[is-1]+1; 26 27 }else{ 28 int p=lower_bound(num,num+is+1,l-x+1)-num; 29 tmp=s[p]; 30 printf("%lld\n",s[p]); 31 } 32 } 33 return 0; 34 }
洛谷P4147 玉蟾宫
- 最大子矩阵问题
- 一行一行的处理,保证统计不会漏掉极大子矩阵.
- 在单调栈里放两个值,下标和长度
- 代码:
1 #include <bits/stdc++.h> 2 #define nmax 1005 3 4 using namespace std; 5 char in[10]; 6 char M[nmax][nmax]; 7 int n,m,is; 8 int t[nmax][nmax],d[nmax][nmax]; 9 int s[nmax][2]; 10 11 int main(){ 12 cin>>n>>m; 13 for (int i=1; i<=n; i++) for (int j=1; j<=m; j++) { scanf("%s",in); M[i][j]=in[0]; } 14 for (int i=1; i<=m; i++) { 15 int nowr=0; 16 for (int j=1; j<=n; j++) { 17 if(M[j][i]=='R') nowr=j; 18 t[j][i]=j-nowr; //可行长度 19 } 20 } 21 //开单调栈一顿扫 22 for (int i=1; i<=n; i++) { 23 is=0; 24 s[is][0]=t[i][1]; 25 s[is][1]=1; 26 for (int j=2; j<=m; j++) { 27 while(t[i][j]<s[is][0]){ //0是值,1是下标 28 int l; 29 for (l=is-1; s[l][0]==s[is][0]&&l>=0; l--){} 30 l=s[l][1]; 31 d[i][ s[is][1] ]=s[is][0]*(j-l-1); 32 is--; 33 } 34 is++; 35 s[is][1]=j; 36 s[is][0]=t[i][j]; 37 } 38 for (; is>=0; is--){ 39 int l; 40 for (l=is-1; s[l][0]==s[is][0]&&l>=0; l--){} 41 l=s[l][1]; 42 d[i][ s[is][1] ]=s[is][0]*(m-l); 43 } 44 } 45 int ans=0; 46 for (int i=1; i<=n; i++) for (int j=1; j<=m; j++) ans=max(ans,d[i][j]); 47 cout<<3*ans<<endl; 48 return 0; 49 }
BZOJ1057 [ZJOI2007]棋盘制作
- 跟上面一题一样,注意横排不合法时推入长度为0的
- 代码:
1 #include <bits/stdc++.h> 2 #define nmax 2005 3 4 using namespace std; 5 char in[10]; 6 int M[nmax][nmax]; 7 int n,m,is; 8 int t[nmax][nmax],d[nmax][nmax],d2[nmax][nmax]; 9 int s[nmax][2]; 10 11 void mypop(int i,int x){ //把is弹出去 12 if(!s[is][0]) { is--; return; } 13 int l; 14 for (l=is-1; s[l][0]==s[is][0]&&l>=0; l--){} 15 if( s[l][0]!=s[is][0] ) l=s[l][1]+1; 16 if(is==0) l=1; 17 d[i][ s[is][1] ]=s[is][0]*(x-l+1); 18 int t=min( x-l+1 , s[is][0] ); 19 d2[i][ s[is][1] ]=t*t; 20 is--; 21 } 22 23 int main(){ 24 cin>>n>>m; 25 for (int i=1; i<=n; i++) for (int j=1; j<=m; j++) scanf("%d",&M[i][j]); 26 for (int i=1; i<=m; i++) { 27 int nowr=0; 28 for (int j=1; j<=n; j++) { 29 t[j][i]=j-nowr; //可行长度 30 if(M[j][i]==M[j+1][i]) nowr=j; 31 } 32 } 33 for (int i=1; i<=n; i++) { 34 is=0; 35 s[is][0]=t[i][1];//0是值,1是下标 36 s[is][1]=1; 37 for (int j=2; j<=m; j++) { 38 if( M[i][j] == M[i][j-1] ) { while(is>=0) mypop(i,j-1); s[++is][1]=j-1; s[is][0]=0; } 39 else while(t[i][j]<s[is][0]) mypop(i,j-1); 40 s[++is][1]=j; 41 s[is][0]=t[i][j]; 42 } 43 while(is>=0) mypop(i,m); 44 } 45 int ans1=0,ans2=0; 46 for (int i=1; i<=n; i++) for (int j=1; j<=m; j++) { ans1=max(ans1,d[i][j]); ans2=max(ans2,d2[i][j]); } 47 cout<<ans2<<endl<<ans1<<endl; 48 return 0; 49 }