网站建设及发展/湖南专业关键词优化服务水平
Description
传送门
Solution
Part 1: 题意抽象
结合对顶栈的思想,我们可以将题意抽象为对两个栈的操作。
即,我们将 1,2,3,⋯,n1,2,3,\cdots,n1,2,3,⋯,n 依次放到 A 栈中,2n+1,2n,⋯,n+22n+1,2n,\cdots,n+22n+1,2n,⋯,n+2 依次放到 B 栈中。那么,辉夜所做的事情就是: 先选择 an+1a_{n+1}an+1,然后执行若干次下述操作: ⌈\lceil⌈ 删除一个栈中的某个元素并选另一个栈的栈顶元素 ⌋\rfloor⌋。
Part 2: 巧妙贪心
考虑枚举 l,rl,rl,r,并判断其是否可行。
为方便叙述,我们将所有栈中的数改为 0/10/10/1;000 表示其值不在 [l,r][l,r][l,r] 中,111 表示其值在 [l,r][l,r][l,r] 中。那么,我们的任务就是: 合理操作两个栈,使 111 总不背删除。
首先特判 an+1∈[l,r]a_{n+1} \in [l,r]an+1∈[l,r] 的情况。
考虑贪心。每次取出 A,B 栈的栈顶元素 x,yx,yx,y。若 x,yx,yx,y 不同时为 111,那么就删去其中不为 111 的元素,并选择另一个;若 x,yx,yx,y 均为 111,那么我们就需要将这两个 111 分别与另一个栈中的 000 配对,并选择两个 111,删除两个 000。现在关键在于,该选择何处的 000 呢?
注意到,最不容易被删除的是栈底的元素(因为大部分时间只能用 ⌈\lceil⌈ 删除一个栈的某个元素 ⌋\rfloor⌋ 来删除它)。换言之,栈底的元素是最有用的,因为它是最有时间与机会和另一个栈中的 111 配对。贪心地,令 x,yx,yx,y 为两个栈中最靠近栈顶的 000 的位置即可。
显然,若在某个时刻找不到这样的 000,那么 [l,r][l,r][l,r] 不可行;否则,[l,r][l,r][l,r] 可行。
由于 [l,r][l,r][l,r] 共有 O(n2)O(n^2)O(n2) 个,每次都要 O(n)O(n)O(n) 地判断,所以复杂度为 O(n3)O(n^3)O(n3)。
结合双指针可以做到 O(n2)O(n^2)O(n2),期望得分 606060 分。
Part 3: 性质观察
如果将 [l,r][l,r][l,r] 是否可行直接描述为上述贪心策略,我们似乎很难在修改 0→1,1→00 \to 1,1 \to 00→1,1→0 的同时动态地维护贪心。这启发我们根据贪心,得到一个易于描述的 [l,r][l,r][l,r] 是否可行的推论。
注意到,我们需要判断的就是,是否每个 A,B 栈中的 111 都可以与一个下标大于它的 000 进行匹配,且这些 111 匹配的位置单调向右。因此,有解当且仅当,对于每个 111 向另一个栈中下标大于它的位置连边,那么得到的两个二分图(A 栈 111 →\to→ B 栈 000,B 栈 111 →\to→A 栈 000)均有完美匹配。从而,得到 [l,r][l,r][l,r] 合法的充要条件: 每个后缀中,A 栈中 111 的个数不超过 B 栈中 000 的个数且 B 栈中 111 的个数不超过 A 栈中 000 的个数。
注意到,若满足第一个要求那么第二个要求必定满足,于是只需要判断第一个要求是否满足就足够了。
Part 4: DS维护
令 bib_ibi 表示,[i,n][i,n][i,n] 中 111 的数量减去 [i+n+1,2n+1][i+n+1,2n+1][i+n+1,2n+1] 中 000 的数量。
我们可以在双指针移动的过程中,找到该从 000 变 111 或从 111 变 000 的位置,并对 bbb 序列进行前缀修改。同时,我们还要判断 [l,r][l,r][l,r] 是否可行,即需要查询 bbb 中的最小值是否小于 000。
采用线段树维护即可,时间复杂度 O(nlogn)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;
}