题解:P8304 [CoE R4 D] 01 串

前言

本文同步自洛谷专栏,题目传送门

前置练习:小白逛公园

参考资料:另一篇题解,作者 juruo999 大佬

写作原因:看参考资料的时候,由于太弱了,公式推导有点迷糊。

解题

问题转化

题目中给了前缀和后缀的限制,考虑转化一下:将 \(0\) 视作 \(-1\)

那么限制就可以直接利用前缀和、后缀和表述:任意前缀和非负,任意后缀和非负。

又由于求子序列长度最大值,所以 \(1\) 都要保留,现在需要求解至少删去多少个 \(-1\) 才能满足要求,设当前询问区间 \([L,R]\) 删去的个数\(ans\),我们需要最小化它。

下面的分析将基于询问区间为 \([L,R]\)

对于一个前缀 \([L,i]\),如果和(记作 \(pre_i\))为负数,那么在这个前缀中至少要删去 \(-pre_i\)\(-1\)

对于后缀同理,记 \([i,R]\) 的和为 \(suf_i\)

\(pre_{L-1}=0,suf_{R+1}=0\)

具体分析

首先分析 \(ans\) 的下界:对于一组下标 \(i,j(L\le i<j\le R)\),必须保证 \([L,i],[j,R]\) 合法,

那么就有下界:

\[ans\ge \max_{L-1\le i<j\le R+1}\{-pre_i-suf_j\} \]

注意:边界的扩展是为了处理类似于 \(\forall L\le i\le R,pre_i>0\) 的情况,但由于这种情况在统计答案时可以方便处理,下文将使用 \([L,R]\) 区间。

尝试考虑能否取到这个下界。

首先保证前缀合法,那么在这一步中,对于一个前缀 \([L,i]\),其中删去的 \(-1\) 的个数是 \(\max_{L\le j\le i}\{-pre_j\}\)

\([L,R]\) 区间就删去了 \(\max_{L\le j\le R}\{-pre_j\}\) 个。

下面考虑保证后缀合法。

保证前缀合法的过程中,对后缀也有影响,设 \(suf_i\) 可变成的最大值\(h_i\)

那么,对于能在 \(i\) 右边删的 \(-1\),就尽量不要放在左边,则 \(h_i\)\(suf_i\) 的差值为 \([L,R]\) 中至少要删的数减去 \([L,i)\) 中至少要删的数。即:

\[h_i=suf_i+\max_{L\le j\le R}\{-pre_j\}-\max_{L\le j< i}\{-pre_j\} \]

在这一步中要删的数的个数为 \(\max_{L\le i\le R}\{-h_i\}\)

经过这两步,所有前缀后缀都合法,则在此方案下,至少要删的数的个数为:

\[\max_{L\le i\le R}\{-suf_i-\max_{L\le j\le R}\{-pre_j\}+\max_{L\le j< i}\{-pre_j\}\}+\max_{L\le j\le R}\{-pre_j\} \]

可以消掉两项,并且把里面的 \(\max\) 与外面的合并:

\[\max_{L\le j<i\le R}\{-suf_i+(-pre_j)\} \]

恰好等于下界,故可以取到。

实现

考虑如何快速求出该值。令区间 \([l,r]\) 的和为 \(sum_{l,r}\),区间不合法则 \(sum_{l,r}=0\)

\[\begin{aligned} ans&=\max_{L\le j<i\le R}\{-suf_i-pre_j\}\\ &=\max_{L\le j<i\le R}\{sum_{L,R}-suf_i-pre_j\}-sum_{L,R}\\ &=\max_{L\le j<i\le R}\{sum_{j+1,i-1}\}-sum_{L,R} \end{aligned}\]

\(\max\) 中的形式为最大子段和与 \(0\) 的较大值,求解方式为线段树,具体的见前置练习,

由于 \(ans\) 描述的是删数的个数的最小值,那么最多可以保留的数的个数:

\[R-L+1-\max_{L\le j<i\le R}\{sum_{j+1,i-1}\}+sum_{L,R} \]

复杂度

同前置练习复杂度,时间 \(O(n\log{n})\),空间 \(O(n)\)

代码

注意特判没有合法子序列的情况。

$\red{\text{code}}$
#include<bits/stdc++.h>
using namespace std;
#define N 500005
#define mid ((l+r)>>1)

struct node{
	int pre,suf,sum,maxn;
}tr[N<<2],ret;

char s[N];

inline int read(){
	int a=0;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) a=10*a+ch-'0',ch=getchar();
	return a;
}

void merge(node &a,node &b,node &c){
	a.sum=b.sum+c.sum;
	a.pre=max(b.pre,b.sum+c.pre);
	a.suf=max(c.suf,c.sum+b.suf);
	a.maxn=max(b.maxn,c.maxn);
	a.maxn=max(a.maxn,b.suf+c.pre);
}

void build(int p,int l,int r){
	if(l==r){
		if(s[l]=='0') tr[p]={-1,-1,-1,-1};
		else tr[p]={1,1,1,1};
		return;
	}
	build(p<<1,l,mid),build(p<<1|1,mid+1,r);
	merge(tr[p],tr[p<<1],tr[p<<1|1]); 
}

void query(int p,int l,int r,int ql,int qr,node &x){
	if(ql<=l&&r<=qr) return x=tr[p],void();
	if(ql>mid) return query(p<<1|1,mid+1,r,ql,qr,x),void();
	if(mid>=qr) return query(p<<1,l,mid,ql,qr,x),void();
	node y,z;
	query(p<<1,l,mid,ql,qr,y),query(p<<1|1,mid+1,r,ql,qr,z);
	merge(x,y,z);
}

int main(){
	int n=read(),q=read();
	scanf(" %s",s+1),build(1,1,n);
	for(int i=1;i<=q;i++){
		int l=read(),r=read(),res;
		query(1,1,n,l,r,ret); 
		res=r-l+1+ret.sum-max(0,ret.maxn);
		if(res==0) puts("-1");
		else printf("%d\n",res);
	}
	return 0;
} 

posted @ 2026-06-05 10:07  Wxb2010  阅读(11)  评论(0)    收藏  举报