P7838 解题报告
题解
method 1 暴力
信仰 \(2^n\),谁试谁爽。
method 2 观察性质
首先取掉 \(a_{n+1}\),然后就不管它了,现在还剩下 \(2n\) 个数。
在不取数的情况下,数列中点在第 \(n\) 个数和第 \(n+1\) 个数之间。每次在删除一个数后,中点会移动到第 \(n\) 个数或者第 \(n+1\) 个数。并且容易发现如果删除的数在左边的一段,中间的数会往第 \(n\) 个数移;如果删除的数在右边的一段,中间的数会往第 \(n+1\) 个数移。并且取掉这个数以后,原来被中点分成的两端现在仍然被中点隔开,也就是说,我们始终是在前一半取一个数,后一半取一个数,并且这两个数中至少有一个与中点相邻,所以我们把整个数列看成两个子数列 \(A\) 和 \(B\),其中 \(A\) 为 \(a_1\)~\(a_n\),\(B\) 为 \(a_{2n+1}\)~\(a_{n+2}\)。我们每次进行的操作就是从一个数列中取出最后一个数,再从另外一个数列中删掉任意的一个数,询问最多取出多长的连续数字。
我们在完成一个取数然后抵消的事情,如果某个数列(假设是数列 \(A\))底部的某个数在所枚举区间内,我们肯定要想办法把它拿出来,并且 \(B\) 中和这个要选的数抵消的数一定不能在区间内,那我们取哪个数比较好呢?显然,如果选较前的数,较后的数就容易跑到数列的末端,如果两个不在区间内的数都到了序列末端,那就必须取掉至少一个,就相当于浪费了这个原本可以抵消掉的数。所以我们应该每次尽量选靠后的数来抵消,所以每次取 \(B\) 最后一个不在区间内的数和 \(A\) 的最后一个抵消就好了。当然,如果 \(A\) 和 \(B\) 尾端的两个数都不是区间内的数,那就只能被迫让它们互相抵消。所以如果枚举到某个在区间内的数,另一个数列中没有能与它抵消的了,这个区间就不满足答案应该有的性质;如果成功抵消完了,这个区间就是满足的。
然后似乎就不能在区间这个条件上动手脚,就只能枚举这段连续数字了,然后模拟上述操作,依次判断,时间复杂度 \(O(n^3)\)。
method 3 双指针
显然如果区间 \([l,r]\) 是满足的,那么区间 \([l+1,r]\) 和 \([l,r-1]\) 就都是满足的。对于这种性质,我们可以双指针维护,就可以不用分别枚举 \(l\) 和 \(r\) 了,可以做到 \(O(n^2)\)。
method 4 线段树
发现双指针每次只修改一个数,如果每次都重新模拟一遍很亏,所以考虑能不能数据结构维护。
因为我们每次都是取最靠近 \(B\) 尾端的一个非区间内的数来抵消掉 \(A\) 的最后一个数,只考虑 \(A\) 在区间内数的抵消情况时,如果 \(A\) 数列中靠前的数都能有 \(B\) 数列中的数与其抵消,并且 \(B\) 数列中截至这一位不在区间内的数的数量大于等于 \(A\) 数列截至这一位在区间内的数的数量时,\(A\) 数列截至这一位都是能被抵消的,所以我们可以记录 \(A\) 数列在区间内的数的数量的前缀数组和 \(B\) 数列不在区间内的数的数量的前缀数组,保证 \(A\) 的前缀数组每一位都大于 \(B\) 的前缀数组。考虑 \(B\) 在区间内的数抵消情况同理,但是因为 \(A\) 和 \(B\) 中的数的数量是相等的,所以如果 \(A\) 中在区间内的数的数量大于 \(B\) 中在不区间内的数的数量,那么 \(B\) 中在区间内的数的数量一定大于 \(A\) 中在不区间内的数的数量,所以考虑其中一个就可以了。
那么用线段树维护 \(A\) 的前缀数组与 \(B\) 的前缀数组的差,使其最小值保持在 \(0\) 以上即可,复杂度 \(O(nlogn)\)。
代码
#include<bits/stdc++.h>
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a<b)?a:b)
using namespace std;
const int N=4e5+50;
int n;
int a[N],loc[N];
int delt[N];
struct Tree {
int l,r;
int ans;
int tag;
}tr[N*4];
void pushup(int p) {
tr[p].ans=min(tr[p<<1].ans,tr[(p<<1)|1].ans);
}
void pushdown(int p) {
int tag=tr[p].tag;
tr[p<<1].tag+=tag;
tr[(p<<1)|1].tag+=tag;
tr[p<<1].ans+=tag;
tr[(p<<1)|1].ans+=tag;
tr[p].tag=0;
}
void build(int l,int r,int p) {
tr[p].l=l,tr[p].r=r;
if(l==r) {
tr[p].ans=l;
return;
}
int mid=l+r>>1;
build(l,mid,p<<1);
build(mid+1,r,(p<<1)|1);
pushup(p);
}
void change(int l,int r,int x,int p) {
if(tr[p].l>=l&&tr[p].r<=r) {
tr[p].ans+=x;
tr[p].tag+=x;
return;
}
pushdown(p);
int mid=tr[p].l+tr[p].r>>1;
if(mid>=l) change(l,r,x,p<<1);
if(mid<r) change(l,r,x,(p<<1)|1);
pushup(p);
}
int ans;
int main() {
scanf("%d",&n);
for(int i=1;i<=n*2+1;++i) scanf("%d",&a[i]),loc[a[i]]=i;
build(1,n,1);
int l=1,r=0;
while(r<=n*2+1) {
if(tr[1].ans<0) {
l++;
if(loc[l-1]==n+1) continue;
else if(loc[l-1]<n+1) change(loc[l-1],n,1,1);
else change(2*n+2-loc[l-1],n,1,1);
}
else {
ans=max(ans,r-l+1);
r++;
if(loc[r]==n+1) continue;
else if(loc[r]<n+1) change(loc[r],n,-1,1);
else change(2*n+2-loc[r],n,-1,1);
}
}
printf("%d\n",ans);
}