P7838 解题报告

题目:P7838 「Wdoi-3」夜雀 treating

题解

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);
}
posted @ 2022-05-09 17:32  Foraino0267  阅读(35)  评论(0编辑  收藏  举报