栈 属于 一种最基本的数据结构 具体的 维护一个一个序列 且这个序列中的元素满足先进后出 或者 后进先出类似于火车进站 可以想象一下。
而单调的栈 具有一些性质:
1 单调栈里的元素具有单调性
2 元素被加入到栈前 会在栈顶把破坏栈单调性的元素都删除。
3 使用单调栈可以找到元素向左遍历第一个比他小的元素 也可以找到元素向左遍历第一个比他大的元素。(显然 这是单调性的应用吧)
当然 也有一些其他栈的变形 辅助贪心 如 LIS 维护数列的性质 :可持久化单调栈 什么的。还有一些比较有意思的是 对顶栈维护整个序列。栈还和卡特兰数有关 这些坑都填一下吧。
对顶栈:LINK:HDU4699
题目意思让你维护一个数列和一个光标 有多个操作 模拟每个操作 并输出对应结果。Q<=1e6
I x 在光标位置插入一个x 插入完后光标移动到x之后 D 删除光标前一个数字 L 光标向左移 R 光标向右移 Q k 在k之前的最大前缀和 且k不超过当前光标位置。
这个题目很有意思由于动态的插入数字我们可以考虑BST 或者 treap splay 。我乍一看是splay 维护区间位置尽管这样做也可以但是对于Q k 的询问呢?设f[i]表示i之前的前缀最大值 s[i]表示前i个数的和。
那么显然有 s[i]+=s[i-1] f[i]=max(f[i-1].s[i]); 由于每次询问都是在光标之前我们可以只维护光标之前的这个东西 并且光标每次至多移动1个位置我们可以动态的维护这个东西了。
复杂度Qlogn 不过写起来会比较麻烦也没有必要 但是貌似没有更好的解决方法了 此时栈的作用就非常的大了。
当然也可以叫做对顶队列 我们发现 由于每次都是动态插入 维护一个 队列的话每次移动是O(n)的 由于每次只在光标处+数字所以可以维护两个队列一个是光标前的一个是光标后的。这样做就舒服多了。
wa 了近乎3次 我是真的菜 操作可能不合法 f[0]=-INF 多组数据 直接怀疑人生啊 这就是菜鸡的表现没有认真读题(题目是英文的我懒得多看两眼...
//#include<bits/stdc++.h> #include<iostream> #include<ctime> #include<cstdio> #include<cmath> #include<cctype> #include<cstring> #include<string> #include<queue> #include<stack> #include<deque> #include<map> #include<set> #include<bitset> #include<algorithm> #include<utility> #include<vector> #include<iomanip> #include<cstdlib> #define INF 2000000000 #define min(x,y) ((x)>(y)?(y):(x)) #define max(x,y) ((x)>(y)?(x):(y)) #define db double #define RE register #define EPS 1e-8 #define ll long long #define ull unsigned long long using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } const int MAXN=1000010; int Q; char c[5]; int top,top1; int a[MAXN],b[MAXN],f[MAXN],s[MAXN]; int main() { //freopen("1.in","r",stdin); while(scanf("%d",&Q)==1) { top=top1=0;f[0]=-INF; for(int i=1;i<=Q;++i) { int x; scanf("%s",c+1); if(c[1]=='I') { x=read(); a[++top]=x; s[top]=s[top-1]+a[top]; f[top]=max(f[top-1],s[top]); } if(c[1]=='D')--top; if(c[1]=='L') { if(!top)continue; b[++top1]=a[top]; --top; } if(c[1]=='R') { if(!top1)continue; a[++top]=b[top1]; s[top]=s[top-1]+a[top]; f[top]=max(f[top-1],s[top]); --top1; } if(c[1]=='Q') { x=read(); printf("%d\n",f[x]); } } } return 0; }
值得一提的是这种方法的复杂度是O(n) 的而且还好写。值得推荐。
卡特兰数:是组合数学中一个长出现在各种计数问题的数列。(前两天某人hzk还叫我去学组合数学...的确到现在才发现数据结构就是坑 学多了没好处 不如来点思维的感受一下思考的快乐。
卡特兰数 数列 : 1 1 2 5 14 42 132 429...看出来什么没貌似没有很显然的递推关系 的确很难确定一个公式 因为这个公式是个组合数 光看的话显然很难看出来。
Cn =C0Cn-1+C1Cn-2+...Cn-1C0(n>=2)其中C0=1 C1=1; 哇这原来是一个对称的式子。这意味着 我们之间去递推好像是n^2的。
1 卡塔兰数就是研究栈的问题的。栈的问题模型:给出n个数1~n 和一个栈。每个数都要进出栈一次如果进入栈的顺序是 1,2,3,...n那么可能的出栈序列有多少种。
其中 上述Cn 就是 n个数 答案。(这个公式 找规律应该是可以找到的吧不过看起来有点困难...
自然有通项公式(斐波那契都有通项这个怎么会没有:Cn=c(2n,n)-c(2n,n+1)通过非常简单的变换可得->Cn=C(2n,n)/(n+1);
众所周知求组合数 的复杂度可以是O(n)的 那么这个求Cn得复杂度也可以是O(n)的。
考虑为什么这个问题的答案会是Cn呢?关于证明 见算法进阶P49 。
2 这个还和二叉树构成有关 问n个节点 能构成多少棵不同的二叉树 因为编号不影响结果 所以考虑中序遍历 根节点为第k个出栈 那么就是左边的Ck-1*Cn-k这东西其中k取0~n-1那么就和上述式子一毛一样了。
3 凸多边形的三角形划分。这里就比较玄学了 我看网上有的说的感觉不太对...下面是我的理解 由于划分的顺序和 划分的数量无关我们先选择一条边作为划分基底。
此时连边 以基底为三角形的底边 划分 左边形成一个i边形 右边形成一个 n-i+2变形 其中(i>=3)可以显然 问题规模不断缩小(至少缩小1...
其实就是 Cn=Ci*C(n-i+2) 其中i€[3,n-1] 令ti=c(i+3) ti=上述式子 感觉很稳...(口胡的,,,
4
5
6
7
还有这么多 我看不下去了 往后了...
表达式计算 中缀表达式就那样O(n)的求 不会的话见 代码(luogu 1054)
//#include<bits/stdc++.h> #include<iostream> #include<cstdio> #include<iomanip> #include<cstring> #include<string> #include<cstdlib> #include<cmath> #include<algorithm> #include<cctype> #include<utility> #include<set> #include<bitset> #include<queue> #include<stack> #include<deque> #include<map> #include<vector> #include<ctime> #define INF 2147483646 #define ll long long #define R register #define mod 131 using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } const int MAXN=55; char s[MAXN]; int n,sl,pos; char c[MAXN][MAXN]; int flag[MAXN],vis[MAXN]; stack<int>num; stack<char>op; void get(char *a) { for(int i=0;i<=52;i++)a[i]='\0'; char c=getchar();pos=0; while(c=='\n'||c=='\r') c=getchar(); while(c!='\n'&&c!='\r') { a[++pos]=c; c=getchar(); } } inline int fast_pow(int b,int p) { int ans=1; for(int i=1;i<=p;++i)ans=ans*b%mod; return ans; } inline void js() { int x=num.top()%mod,ss;num.pop(); int xx=num.top()%mod;num.pop(); char w=op.top();op.pop(); if(w=='+')ss=x+xx; if(w=='-')ss=xx-x; if(w=='*')ss=x*xx%mod; if(w=='^')ss=fast_pow(xx,x); num.push(ss%mod); } inline int calc(int x,char *w,int len) { while(num.size())num.pop(); while(op.size())op.pop(); for(int i=1;i<=len+1;++i)op.push('('); w[len+1]=')'; for(int i=1;i<=len+1;++i) { if(w[i]==' ')continue; //cout<<w[i]<<endl; if((w[i]>='0'&&w[i]<='9')||w[i]=='a') { if(w[i]=='a'){num.push(x);continue;} int s=w[i]-'0'; while(w[i+1]>='0'&&w[i+1]<='9') { s=s*10+w[i+1]-'0'; ++i; } num.push(s%mod); } else { if(w[i]=='(')op.push(w[i]); if(w[i]=='+'||w[i]=='-') { while(op.top()!='(')js(); op.push(w[i]); } if(w[i]=='*') { while(op.top()=='*'||op.top()=='^')js(); op.push(w[i]); } if(w[i]=='^') { while(op.top()=='^')js(); op.push(w[i]); } if(w[i]==')') { while(op.top()!='(')js(); op.pop(); } } } return (num.top()%mod+mod)%mod; } int main() { //freopen("1.in","r",stdin); //scanf("%s",s+1); get(s);sl=pos; //for(int i=1;i<=sl;++i)cout<<s[i]; n=read(); for(int i=1;i<=n;++i)get(c[i]),flag[i]=pos; for(int i=1;i<=10;++i) { int ans=calc(i,s,sl); //cout<<ans<<endl; for(int j=1;j<=n;++j) { if(vis[j])continue; if(ans!=calc(i,c[j],flag[j]))vis[j]=1; //cout<<calc(i,c[j],flag[j])<<endl; } //printf("%d\n",ans); } for(int i=1;i<=n;++i) { if(vis[i])continue; printf("%c",char('A'+i-1)); } return 0; }
单调栈:
LINK:SP1805
求水平线上最大矩阵面积。n<=100000;考虑答案的必要性 下界max{每一个矩形的面积}。
考虑矩形联系起来怎么统计到答案上 貌似不好搞 分类讨论一下分析答案的存在性:全部都是递增的矩形。那么答案为每一个矩形的长*后面连续矩形的宽。
考虑递减的矩形 倒着来重复刚才的过程。但是为了得到一个普遍解我们不能倒着来 找到正着的一个普遍寻找规律的答案线索。遇到一个自己比上一个矩阵小 那么就不要上一个矩阵累积中间被弹出矩阵的宽度。
最后答案为 每个矩阵长*前面被弹出的矩阵宽度。这还不够优秀 因为上一个矩阵比当前高那么除了他本身给上上一个带来的贡献 它自己就不能再形成贡献了 扔掉 然后把它看成和我一样高的东西。
考虑参差不齐的矩形(也就是正解):obviosly 我们可以把这一段矩形 看成先递增后递减...然后呢?栈里维护单调递增的矩形就好了。
//#include<bits/stdc++.h> #include<iostream> #include<ctime> #include<cstdio> #include<cmath> #include<cctype> #include<cstring> #include<string> #include<queue> #include<stack> #include<deque> #include<map> #include<set> #include<bitset> #include<algorithm> #include<utility> #include<vector> #include<iomanip> #include<cstdlib> #define INF 1000000000 #define min(x,y) ((x)>(y)?(y):(x)) #define max(x,y) ((x)>(y)?(x):(y)) #define db double #define RE register #define EPS 1e-8 #define ll long long #define ull unsigned long long using namespace std; char buf[1<<15],*fs,*ft; inline char getc() { return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++; } inline ll read() { ll x=0,f=1;char ch=getc(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getc();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getc();} return x*f; } const ll MAXN=100010; ll n,top,ans; ll a[MAXN],s[MAXN],w[MAXN]; int main() { //freopen("1.in","r",stdin); while(1) { n=read();ans=0; if(!n)break; for(ll i=1;i<=n;++i)a[i]=read(); a[n+1]=-1;s[0]=-1;top=0; for(ll i=1;i<=n+1;++i) { if(a[i]>=s[top]) { s[++top]=a[i]; w[top]=1; } else { ll p=0; while(a[i]<s[top]) { p+=w[top]; ans=max(ans,s[top]*p); --top; } s[++top]=a[i];w[top]=p+1; } } printf("%lld\n",ans); } return 0; }
LINK: luogu3722