P7838 「Wdoi-3」夜雀 treating 题解

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 01,10 的同时动态地维护贪心。这启发我们根据贪心,得到一个易于描述的 [ 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;
}

猜你喜欢

转载自blog.csdn.net/Cherrt/article/details/119841874