问题即是要对一个栈支持:1.加入一个元素;2.删除最早加入的元素(各有$m$次)
做法1(题解中的算法2)
将栈中的元素标记为01,并按如下方式维护:
1.对于加入操作,直接将其加入并标记为1
2.对于删除操作,对其分类讨论——
(1)若栈顶标记为0,直接弹出即可
(2)若栈顶标记为1,不断弹出栈顶元素直至弹出的01标记个数相同(或栈为空)
进一步的,将弹出的元素放回栈中,再对其分类讨论——
a.若最终栈为空,则将所有标记为1的元素逆序放回并重新标记为0
b.若最终栈不为空,则将所有标记为1的元素顺序放回
不论哪一种情况,最终在将所有标记为0的元素顺序放回并弹出栈顶即可
关于正确性,可以归纳元素的加入顺序为:从栈顶到栈顶标记为0的元素、从栈底到栈顶标记为1的元素,进而显然正确
关于操作次数,可以以逆序对数(形如$(x,y)$满足$x$比$y$早插入而$x$更靠近栈底)除以$\sqrt{m}$为势能,考虑上述过程:
1.显然均摊复杂度为$o(\sqrt{m})$
2.(1)显然均摊复杂度为$o(1)$
2.(2)假设弹出了$x$个1标记和$y$个0标记,那么实际复杂度为$o(x+y)$
注意到1标记内是逆序的且第$i$个0标记前至少有$i$个1标记,因此本来至少有${x\choose 2}+\sum_{i=1}^{y}i$个逆序对
2.(2).a其使得逆序对数为0,那么均摊复杂度为$o(x+y-\frac{x^{2}+y^{2}}{\sqrt{m}})=o(\sqrt{m})$
2.(2).b其使得逆序对数为${x\choose 2}$且有$x=y$,那么均摊复杂度为$o(x-\frac{x^{2}}{\sqrt{m}})=o(\sqrt{m})$
(上述已经包含了"将所有标记为0的元素顺序放回并弹出栈顶"的过程)
另外,势能范围为$[0,m\sqrt{m}]$,因此势能差至多为$o(m\sqrt{m})$
综上,总复杂度(操作次数)为$o(m\sqrt{m})$,由于常数优秀,可以通过
1 #include<bits/stdc++.h> 2 #include "subway.h" 3 using namespace std; 4 #define N 200005 5 #define fi first 6 #define se second 7 int n,m,lim,ans; 8 vector<int>v[2]; 9 stack<pair<int,int> >st; 10 void init(int nn,int mm,int l){ 11 n=nn,m=mm,lim=l,ans=0; 12 while (!st.empty())st.pop(); 13 } 14 void add(int k,int p){ 15 merge(k),st.push(make_pair(k,p)); 16 } 17 void del(){ 18 undo(),st.pop(); 19 } 20 void Add(int k){ 21 add(k,1); 22 } 23 void Del(){ 24 if (!st.top().se){ 25 del(); 26 return; 27 } 28 v[0].clear(),v[1].clear(); 29 int x=1; 30 v[1].push_back(st.top().fi); 31 del(); 32 while ((!st.empty())&&(x)){ 33 x+=(st.top().se<<1)-1; 34 v[st.top().se].push_back(st.top().fi); 35 del(); 36 } 37 if (st.empty()){ 38 for(int i=0;i<v[1].size();i++)add(v[1][i],0); 39 } 40 else{ 41 for(int i=(int)v[1].size()-1;i>=0;i--)add(v[1][i],1); 42 } 43 for(int i=(int)v[0].size()-1;i>=0;i--)add(v[0][i],0); 44 del(); 45 } 46 int solve(int k){ 47 while ((ans<m)&&(check(ans+1)))Add(++ans); 48 Del(); 49 return ans; 50 }
做法2(题解中的算法4,即标算)
仍将栈中的元素标记为01,并按如下方式维护:
1.对于加入操作,直接将其加入并标记为1
2.对于删除操作,记$cnt$为栈中0标记的个数,若$cnt=0$则将全栈重构(翻转),并均标记为0
进一步的,对其分类讨论:
(1)若栈顶标记为0,直接弹出即可
(2)若栈顶标记为1,弹出栈顶连续的一段标记为1的元素和之后的$lowbit(cnt)$个元素(必然均标记为0),然后先将1标记的元素顺序放回、再将所有0标记的元素
关于正确性,可以归纳证明以下两点:
1.元素的加入顺序为:从栈顶到栈顶0标记的元素、从栈底到栈顶1标记的元素
2.从栈顶到栈底不断取出前$lowbit(cnt)$个标记为0的元素(注意$cnt$会因此变化),总是连续的
进而显然正确
关于操作次数,可以以"所有1标记后(到栈底)0标记素个数二进制下1的位数+1和"为势能(结合归纳的第2点),考虑上述过程:
1.显然均摊复杂度为$o(\log m)$
2.注意到这样会减少栈大小的势能,因此均摊复杂度为$o(1)$
2.(1)显然均摊复杂度为$o(1)$
2.(2)注意到弹出的标记为1的元素均会减少1的势能贡献,因此这一部分均摊复杂度为$o(1)$
对于这$lowbit(cnt)$个元素,注意到$cnt$增长只有变为0时,而$\sum_{i=1}^{n}lowbit(i)\le \sum_{k=1}^{\log_{2}n}2^{k}\frac{n}{2^{k}}=o(n\log n)$,那么将所有累加起来后仍时$o(m\log m)$的
另外,势能范围为$[0,m\log m]$,因此势能差至多为$o(m\log m)$
综上,总复杂度(操作次数)为$o(m\log m)$(但实际表现甚至不如做法1),可以通过
1 #include<bits/stdc++.h> 2 #include "subway.h" 3 using namespace std; 4 #define N 200005 5 #define fi first 6 #define se second 7 int n,m,lim,cnt,ans; 8 vector<int>v[2]; 9 stack<pair<int,int> >st; 10 void init(int nn,int mm,int l){ 11 n=nn,m=mm,lim=l,cnt=ans=0; 12 while (!st.empty())st.pop(); 13 } 14 int lowbit(int k){ 15 return (k&(-k)); 16 } 17 void add(int k,int p){ 18 merge(k),st.push(make_pair(k,p)); 19 } 20 void del(){ 21 undo(),st.pop(); 22 } 23 void Add(int k){ 24 add(k,1); 25 } 26 void Del(){ 27 if (!cnt){ 28 v[1].clear(); 29 while (!st.empty()){ 30 v[1].push_back(st.top().fi); 31 del(); 32 } 33 for(int i=0;i<v[1].size();i++)add(v[1][i],0); 34 cnt=st.size(); 35 } 36 if (!st.top().se){ 37 del(); 38 return; 39 } 40 v[0].clear(),v[1].clear(); 41 while (st.top().se==1){ 42 v[1].push_back(st.top().fi); 43 del(); 44 } 45 for(int i=0;i<lowbit(cnt);i++){ 46 v[0].push_back(st.top().fi); 47 del(); 48 } 49 for(int i=(int)v[1].size()-1;i>=0;i--)add(v[1][i],1); 50 for(int i=(int)v[0].size()-1;i>=0;i--)add(v[0][i],0); 51 del(); 52 } 53 int solve(int k){ 54 while ((ans<m)&&(check(ans+1)))Add(++ans); 55 Del(),cnt--; 56 return ans; 57 }