[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)$满足$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 }
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(\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 }
View Code

 

posted @ 2021-12-19 13:11  PYWBKTDA  阅读(124)  评论(5编辑  收藏  举报