[uoj693]地铁规划
问题即是要对一个栈支持:1.加入一个元素;2.删除最早加入的元素(各有次)
做法1(题解中的算法2)
将栈中的元素标记为01,并按如下方式维护:
1.对于加入操作,直接将其加入并标记为1
2.对于删除操作,对其分类讨论——
(1)若栈顶标记为0,直接弹出即可
(2)若栈顶标记为1,不断弹出栈顶元素直至弹出的01标记个数相同(或栈为空)
进一步的,将弹出的元素放回栈中,再对其分类讨论——
a.若最终栈为空,则将所有标记为1的元素逆序放回并重新标记为0
b.若最终栈不为空,则将所有标记为1的元素顺序放回
不论哪一种情况,最终在将所有标记为0的元素顺序放回并弹出栈顶即可
关于正确性,可以归纳元素的加入顺序为:从栈顶到栈顶标记为0的元素、从栈底到栈顶标记为1的元素,进而显然正确
关于操作次数,可以以逆序对数(形如满足比早插入而更靠近栈底)除以为势能,考虑上述过程:
1.显然均摊复杂度为
2.(1)显然均摊复杂度为
2.(2)假设弹出了个1标记和个0标记,那么实际复杂度为
注意到1标记内是逆序的且第个0标记前至少有个1标记,因此本来至少有个逆序对
2.(2).a其使得逆序对数为0,那么均摊复杂度为
2.(2).b其使得逆序对数为且有,那么均摊复杂度为
(上述已经包含了"将所有标记为0的元素顺序放回并弹出栈顶"的过程)
另外,势能范围为,因此势能差至多为
综上,总复杂度(操作次数)为,由于常数优秀,可以通过

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.对于删除操作,记为栈中0标记的个数,若则将全栈重构(翻转),并均标记为0
进一步的,对其分类讨论:
(1)若栈顶标记为0,直接弹出即可
(2)若栈顶标记为1,弹出栈顶连续的一段标记为1的元素和之后的个元素(必然均标记为0),然后先将1标记的元素顺序放回、再将所有0标记的元素
关于正确性,可以归纳证明以下两点:
1.元素的加入顺序为:从栈顶到栈顶0标记的元素、从栈底到栈顶1标记的元素
2.从栈顶到栈底不断取出前个标记为0的元素(注意会因此变化),总是连续的
进而显然正确
关于操作次数,可以以"所有1标记后(到栈底)0标记素个数二进制下1的位数+1和"为势能(结合归纳的第2点),考虑上述过程:
1.显然均摊复杂度为
2.注意到这样会减少栈大小的势能,因此均摊复杂度为
2.(1)显然均摊复杂度为
2.(2)注意到弹出的标记为1的元素均会减少1的势能贡献,因此这一部分均摊复杂度为
对于这个元素,注意到增长只有变为0时,而,那么将所有累加起来后仍时的
另外,势能范围为,因此势能差至多为
综上,总复杂度(操作次数)为(但实际表现甚至不如做法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 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
2020-12-19 [loj2850]无进位加法