Description
Solution
Part 1: 题意抽象
结合对顶栈的思想,我们可以将题意抽象为对两个栈的操作。
即,我们将 1 , 2 , 3 , ⋯ , n 1,2,3,\cdots,n 1,2,3,⋯,n 依次放到 A 栈中, 2 n + 1 , 2 n , ⋯ , n + 2 2n+1,2n,\cdots,n+2 2n+1,2n,⋯,n+2 依次放到 B 栈中。那么,辉夜所做的事情就是: 先选择 a n + 1 a_{n+1} an+1,然后执行若干次下述操作: ⌈ \lceil ⌈ 删除一个栈中的某个元素并选另一个栈的栈顶元素 ⌋ \rfloor ⌋。
Part 2: 巧妙贪心
考虑枚举 l , r l,r l,r,并判断其是否可行。
为方便叙述,我们将所有栈中的数改为 0 / 1 0/1 0/1; 0 0 0 表示其值不在 [ l , r ] [l,r] [l,r] 中, 1 1 1 表示其值在 [ l , r ] [l,r] [l,r] 中。那么,我们的任务就是: 合理操作两个栈,使 1 1 1 总不背删除。
首先特判 a n + 1 ∈ [ l , r ] a_{n+1} \in [l,r] an+1∈[l,r] 的情况。
考虑贪心。每次取出 A,B 栈的栈顶元素 x , y x,y x,y。若 x , y x,y x,y 不同时为 1 1 1,那么就删去其中不为 1 1 1 的元素,并选择另一个;若 x , y x,y x,y 均为 1 1 1,那么我们就需要将这两个 1 1 1 分别与另一个栈中的 0 0 0 配对,并选择两个 1 1 1,删除两个 0 0 0。现在关键在于,该选择何处的 0 0 0 呢?
注意到,最不容易被删除的是栈底的元素(因为大部分时间只能用 ⌈ \lceil ⌈ 删除一个栈的某个元素 ⌋ \rfloor ⌋ 来删除它)。换言之,栈底的元素是最有用的,因为它是最有时间与机会和另一个栈中的 1 1 1 配对。贪心地,令 x , y x,y x,y 为两个栈中最靠近栈顶的 0 0 0 的位置即可。
显然,若在某个时刻找不到这样的 0 0 0,那么 [ l , r ] [l,r] [l,r] 不可行;否则, [ l , r ] [l,r] [l,r] 可行。
由于 [ l , r ] [l,r] [l,r] 共有 O ( n 2 ) O(n^2) O(n2) 个,每次都要 O ( n ) O(n) O(n) 地判断,所以复杂度为 O ( n 3 ) O(n^3) O(n3)。
结合双指针可以做到 O ( n 2 ) O(n^2) O(n2),期望得分 60 60 60 分。
Part 3: 性质观察
如果将 [ l , r ] [l,r] [l,r] 是否可行直接描述为上述贪心策略,我们似乎很难在修改 0 → 1 , 1 → 0 0 \to 1,1 \to 0 0→1,1→0 的同时动态地维护贪心。这启发我们根据贪心,得到一个易于描述的 [ l , r ] [l,r] [l,r] 是否可行的推论。
注意到,我们需要判断的就是,是否每个 A,B 栈中的 1 1 1 都可以与一个下标大于它的 0 0 0 进行匹配,且这些 1 1 1 匹配的位置单调向右。因此,有解当且仅当,对于每个 1 1 1 向另一个栈中下标大于它的位置连边,那么得到的两个二分图(A 栈 1 1 1 → \to → B 栈 0 0 0,B 栈 1 1 1 → \to →A 栈 0 0 0)均有完美匹配。从而,得到 [ l , r ] [l,r] [l,r] 合法的充要条件: 每个后缀中,A 栈中 1 1 1 的个数不超过 B 栈中 0 0 0 的个数且 B 栈中 1 1 1 的个数不超过 A 栈中 0 0 0 的个数。
注意到,若满足第一个要求那么第二个要求必定满足,于是只需要判断第一个要求是否满足就足够了。
Part 4: DS维护
令 b i b_i bi 表示, [ i , n ] [i,n] [i,n] 中 1 1 1 的数量减去 [ i + n + 1 , 2 n + 1 ] [i+n+1,2n+1] [i+n+1,2n+1] 中 0 0 0 的数量。
我们可以在双指针移动的过程中,找到该从 0 0 0 变 1 1 1 或从 1 1 1 变 0 0 0 的位置,并对 b b b 序列进行前缀修改。同时,我们还要判断 [ l , r ] [l,r] [l,r] 是否可行,即需要查询 b b b 中的最小值是否小于 0 0 0。
采用线段树维护即可,时间复杂度 O ( n log n ) O(n \log n) O(nlogn)。本题被解决。
题外话
其实,我们可以通过打表的方式,直接看出 Part 3 中的结论。估计,对于我这种蒟蒻,如果真的想要在赛时迅速地切掉此题,这是唯一的捷径吧。
Code
#include <bits/stdc++.h>
using namespace std;
const int maxl=400005;
int read(){
int s=0,w=1;char ch=getchar();
while (ch<'0'||ch>'9'){
if (ch=='-') w=-w;ch=getchar();}
while (ch>='0'&&ch<='9'){
s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}
return s*w;
}
int n,r,ans;
int a[maxl],p[maxl],tree[maxl<<2],tag[maxl<<2];
void pushup(int rt){
tree[rt]=min(tree[rt<<1],tree[rt<<1|1]);}
void F(int rt,int k){
tree[rt]+=k,tag[rt]+=k;}
void pushdown(int rt){
if (tag[rt]) F(rt<<1,tag[rt]),F(rt<<1|1,tag[rt]),tag[rt]=0;
}
void build_tree(int l,int r,int rt){
if (l==r){
tree[rt]=(n-l+1);
return;
}
int mid=(l+r)>>1;
build_tree(l,mid,rt<<1),build_tree(mid+1,r,rt<<1|1);
pushup(rt);
}
void change(int nl,int nr,int l,int r,int rt,int k){
if (nl<=l&&r<=nr){
F(rt,k);
return;
}
pushdown(rt);
int mid=(l+r)>>1;
if (nl<=mid) change(nl,nr,l,mid,rt<<1,k);
if (nr>mid) change(nl,nr,mid+1,r,rt<<1|1,k);
pushup(rt);
}
void Modify(int rt,int x){
int id=(p[rt]>n)?(p[rt]-n-1):(n-p[rt]+1);
if (1<=id&&id<=n) change(1,id,1,n,1,x);
}
signed main(){
n=read();
for (int i=1;i<=2*n+1;i++) a[i]=read(),p[a[i]]=i;
build_tree(1,n,1);
for (int l=1;l<=2*n+1;l++){
while (r<2*n+1){
Modify(++r,-1);
if (tree[1]<0) {
Modify(r--,1);break;}
}
ans=max(ans,r-l+1);
Modify(l,1);
}
cout<<ans<<endl;
return 0;
}