思路:
要注意到新增 \(1\) 和刪除 \(0\) 是等價的。
先令 \(0 \to -1\)。
首先猜了一個結論,先順著走,做一個字首和,若當且位置的字首和 \(<0\),那麼需要刪除這個位置的 \(0\),使得字首和為正;然後再反著做一遍,那麼答案就是刪除的 \(0\) 的個數。
暴力 Code:
int main(){
n=read(),q=read();
For(i,1,n){
a[i]=get()-'0';
if(!a[i])
a[i]=-1;
}
while(q--){
sum=ans=0;
l=read(),r=read();
For(i,l,r){
sum+=a[i];
if(sum<0){
sum++,ans++;
f[i]=1;
}
}
_For(i,l,r){
if(f[i]){
f[i]=0;
continue;
}
sum+=a[i];
if(sum<0)
sum++,ans++;
}
write(r-l+1-ans);
putchar('\n');
}
return 0;
}
考慮最佳化,令 \(pre_i\) 表示 \([l,i]\) 的字首和,\(suf_i\) 表示 \([i,r]\) 的字尾和。
則正著掃的時候,若 \(pre_i=-1\),相當於將 \(j \in [i,r]\) 的 \(pre_j\) 都增加了 \(1\),繼續掃的時候若碰到 \(pre'_k = -1\),經過前面的加法操作前 \(pre_k = -2\),此時也需要給 \(j \in [k,r]\) 的 \(pre_j\) 增加 \(1\)。
那麼我們就可以發現,後面若第 \(i\) 次找到 \(pre'_j=-1\),則原來的 \(pre_j = -i\)。
同時我們注意到 \(pre_i\) 是由 \(pre_{i-1}\) 加減 \(1\) 變換而來的,則訪問的值域是連續的,則對於第一次正著掃需要的刪除次數為:
然後再考慮反著掃的貢獻,同樣也是找到 \(suf'_i = -1,-2,-3,\cdots\),但是這裡 \(suf'\) 是 \(suf\) 經過正著掃後變化的。
在正著掃的時候,刪除 \(i\) 處的 \(0\),相當於將 \(j \in [l,i]\) 的 \(suf_i\) 加 \(1\),即我們需要維護 \(i\) 這個位置右側被刪除的 \(0\) 的個數,因為正著掃是從左往右的,考慮做個差好維護一些,即相當於 \(-\min\limits_{i=l}^r pre_i\) 減去 \(i\) 左側被刪除的 \(0\) 的個數。
\(i\) 左側被刪除的 \(0\) 的個數,即 \(- \min\limits_{j=l}^i pre_j\),則我們得到了:
則反著掃的貢獻是:
則刪除 \(0\) 的總數為:
考慮這個式子的抽象意義:求出一個區間內不相交的一個字尾和加上一個字首和的最小值,容斥一下,轉化為區間和減去中間的一段,即我們要使得中間的一段最大,即區間最大子段和問題,使用線段樹維護即可。
時間複雜度為 \(O(N \log N)\)。
完整程式碼:
#include<bits/stdc++.h>
#define Add(x,y) (x+y>=mod)?(x+y-mod):(x+y)
#define lowbit(x) x&(-x)
#define pi pair<ll,ll>
#define pii pair<ll,pair<ll,ll>>
#define iip pair<pair<ll,ll>,ll>
#define ppii pair<pair<ll,ll>,pair<ll,ll>>
#define fi first
#define se second
#define full(l,r,x) for(auto it=l;it!=r;it++) (*it)=x
#define Full(a) memset(a,0,sizeof(a))
#define open(s1,s2) freopen(s1,"r",stdin),freopen(s2,"w",stdout);
#define For(i,l,r) for(int i=l;i<=r;i++)
#define _For(i,l,r) for(int i=r;i>=l;i--)
using namespace std;
typedef long double lb;
typedef double db;
typedef unsigned long long ull;
typedef long long ll;
bool Begin;
const ll N=1e6+10;
inline ll read(){
ll x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x*f;
}
inline void write(ll x){
if(x<0){
putchar('-');
x=-x;
}
if(x>9)
write(x/10);
putchar(x%10+'0');
}
inline char get(){
char c;
while(1){
c=getchar();
if(c=='0'||c=='1')
break;
}
return c;
}
ll n,q,l,r,t,ans;
ll a[N],s[N];
class Tree{
public:
struct Node{
ll l,r;
ll L,R;
ll sum;
ll data;
}X[N<<2];
Node pushup(Node A,Node B){
Node Ans;
Ans.l=A.l,Ans.r=B.r;
Ans.sum=A.sum+B.sum;
Ans.L=max(A.L,A.sum+B.L);
Ans.R=max(B.R,B.sum+A.R);
Ans.data=max({A.data,B.data,A.R+B.L});
return Ans;
}
void build(ll k,ll l,ll r){
X[k].l=l,X[k].r=r;
if(l==r){
X[k].sum=a[l];
X[k].data=X[k].L=X[k].R=max(a[l],0ll);
return ;
}
ll mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
X[k]=pushup(X[k<<1],X[k<<1|1]);
}
Node query(ll k,ll l,ll r){
if(X[k].l==l&&r==X[k].r)
return X[k];
ll mid=(X[k].l+X[k].r)>>1;
if(r<=mid)
return query(k<<1,l,r);
else if(l>mid)
return query(k<<1|1,l,r);
else
return pushup(query(k<<1,l,mid),query(k<<1|1,mid+1,r));
}
}T;
bool f[N];
int main(){
// open("A.in","A.out");
n=read(),q=read();
For(i,1,n){
a[i]=get()-'0';
if(!a[i])
a[i]=-1;
s[i]=s[i-1]+a[i];
}
T.build(1,1,n);
while(q--){
l=read(),r=read();
t=(r-l+1)+(s[r]-s[l-1]-T.query(1,l,r).data);
if(!t)
puts("-1");
else{
write(t);
putchar('\n');
}
}
return 0;
}