[uoj693]地铁规划

问题即是要对一个栈支持:1.加入一个元素;2.删除最早加入的元素(各有m次)

做法1(题解中的算法2)

将栈中的元素标记为01,并按如下方式维护:

1.对于加入操作,直接将其加入并标记为1

2.对于删除操作,对其分类讨论——

(1)若栈顶标记为0,直接弹出即可

(2)若栈顶标记为1,不断弹出栈顶元素直至弹出的01标记个数相同(或栈为空)

进一步的,将弹出的元素放回栈中,再对其分类讨论——

a.若最终栈为空,则将所有标记为1的元素逆序放回并重新标记为0

b.若最终栈不为空,则将所有标记为1的元素顺序放回

不论哪一种情况,最终在将所有标记为0的元素顺序放回并弹出栈顶即可

关于正确性,可以归纳元素的加入顺序为:从栈顶到栈顶标记为0的元素、从栈底到栈顶标记为1的元素,进而显然正确

关于操作次数,可以以逆序对数(形如(x,y)满足xy早插入而x更靠近栈底)除以m为势能,考虑上述过程:

1.显然均摊复杂度为o(m)

2.(1)显然均摊复杂度为o(1)

2.(2)假设弹出了x个1标记和y个0标记,那么实际复杂度为o(x+y)

注意到1标记内是逆序的且第i个0标记前至少有i个1标记,因此本来至少有(x2)+i=1yi个逆序对

2.(2).a其使得逆序对数为0,那么均摊复杂度为o(x+yx2+y2m)=o(m)

2.(2).b其使得逆序对数为(x2)且有x=y,那么均摊复杂度为o(xx2m)=o(m)

(上述已经包含了"将所有标记为0的元素顺序放回并弹出栈顶"的过程)

另外,势能范围为[0,mm],因此势能差至多为o(mm)

综上,总复杂度(操作次数)为o(mm),由于常数优秀,可以通过

复制代码
 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 }
View Code
复制代码

 做法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(logm)

2.注意到这样会减少栈大小的势能,因此均摊复杂度为o(1)

2.(1)显然均摊复杂度为o(1)

2.(2)注意到弹出的标记为1的元素均会减少1的势能贡献,因此这一部分均摊复杂度为o(1)

对于这lowbit(cnt)个元素,注意到cnt增长只有变为0时,而i=1nlowbit(i)k=1log2n2kn2k=o(nlogn),那么将所有累加起来后仍时o(mlogm)

另外,势能范围为[0,mlogm],因此势能差至多为o(mlogm)

综上,总复杂度(操作次数)为o(mlogm)(但实际表现甚至不如做法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 }
View Code
复制代码

 

posted @   PYWBKTDA  阅读(169)  评论(5编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
历史上的今天:
2020-12-19 [loj2850]无进位加法
点击右上角即可分享
微信分享提示