给定一个长度为(n)的指令序列和初始变量(x=0),仅包含(+)和(-),(+)表示让变量(x+1),(-)表示让变量(x-1)。然后进行(m)次操作,每次操作指令一个指令区间([l,r])忽略执行这个区间内的指令,其他指令照旧依次执行,输出每次操作变量(x)的不同的数的个数。 例如+-- 变量x变成1,0,-1也就是有3中不同的变化。
hint1
最终的变量的大小是固定的,只需要看(-)的个数和(+)的个数即可,(x=0-sum(-)+sum(+))
hint2
我要找不同的范围,我只需要找到两个边界(L,R)即可,因为我在这两个边界范围内的数的变化不影响答案。例如(+--)我(L=-1,R=1)所以我们的最终答案就是(R-L+1=3)
hint3
这种忽略区间的操作我们需要用前缀和或者后缀和来做,前缀和(lp[i])表示([1,i])内最大的偏移(R)的范围,(lm[i])表示([1,i])内最大偏移(L)的范围,同理(rp[i],rm[i])表示后缀的([i,n])范围内的偏移关系。这样我们每次做的时候就可以(O(1))的操作最终复杂度为(O(N+M))
hint4
对于前缀和,我们只需要处理当前遍历范围内最大的数或者最小的数即可,例如(+--) (lp[3]=1,lm[3]=1)那么对于([l,r])内指令忽略,我只需要让(L=lp[l-1],R=lm[l-1])即可算出左边的指令所影响的答案,然后最终(x)的值可以根据hint1来计算
hint5(关键,比赛的时候这里没有想清楚)
对于后缀和,处理起来就比较的巧妙。当然我们直接按照前缀和的思路去算后缀和的话也可以,但是肯定会超时。因为我们没法直接表示([r+1,n])内的指令直接取出来的情况。那怎么办呢。这里需要跟前缀和不同的思路。
首先对于回文的串来说(+-+)我不论从前面开始用前缀和思路算还是从后面开始用前缀和思路算,肯定答案是一样的。
但是对于不回文的串来说(--+)我从前面用前缀和思路来算是(R=0,L=-2)但是我从后面用前缀和的思路来算是(R=1,L=-1)这显然是不一样的,但是(R-L+1)的值却是一样的那我是不是可以猜想我就用这个后面的前缀和思路更新前面算好的答案就是对的呢,因为我(R-L+1)的值并没有什么变化,仅仅(L,R)不是对的而已。我比赛的时候就是这么猜的,但是当我敲完发现连样例都过不去。。。于是猜想失败,然后凑巧也到点了。
其实这里(L,R)的值他还是有用的。
那么该怎么正确计算他们的值呢?
hint6(这里之前比赛没有想到)
我们可以通过这样的一种思路来计算:
-
如果当前的指令是(+)让(rp[i]=max(rp[i+1],0)+1,rm[i]=rm[i+1]+1)
-
如果当前的指令是(-)让(rp[i]=rp[i+1]-1,rm[i]=min(0,rm[i+1])-1)
其中如果是(+)那么我肯定是要让(rp[i])加一的但是因为可能(rp[i])之前已经被减到(<0)了于是只好让他取个(max)当是(-)的时候同理。
这样两个端点一块更新就可以正确的计算出(rp[i],rm[i])了(需要自己思考正确性)
那么最终答案应该不用多说就可以得出了。最终复杂度(O(N+M))
Code
#include <bits/stdc++.h>
using namespace std;
const int N=200005;
int lp[N],lm[N],rp[N],rm[N],pre[N];
char s[N];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n,m,l,r,L,R;
scanf("%d%d%s",&n,&m,s+1);
pre[0]=lp[0]=lm[0]=rp[n+1]=rm[n+1]=0;
for(int i=1;i<=n;i++)
{
lp[i]=lp[i-1];
lm[i]=lm[i-1];
if(s[i]=='+') pre[i]=pre[i-1]+1;
else pre[i]=pre[i-1]-1;
lp[i]=max(lp[i],pre[i]);
lm[i]=min(lm[i],pre[i]);
}
for(int i=n;i;i--)
{
if(s[i]=='+') rp[i]=max(0,rp[i+1])+1,rm[i]=rm[i+1]+1;
else rp[i]=rp[i+1]-1,rm[i]=min(0,rm[i+1])-1;
}
while(m--)
{
scanf("%d%d",&l,&r);
L=lm[l-1];
R=lp[l-1];
L=min(L,pre[l-1]+rm[r+1]);
R=max(R,pre[l-1]+rp[r+1]);
printf("%d
",R-L+1);
}
}
}