[2020多校A层11.30] 选举
有一个长度为 \(N\) 的字符串 \(S[1\dots N]\),它仅由 \(C\) 和 \(T\) 两种字母组成。
现在有 \(Q\) 个查询,每个查询包含两个整数 \(L\) 和 \(R\) ,表示:设新字符串 \(S'=S[L\dots R]\) ,至少在 \(S'\) 中要删除多少个字符,才能保证:对于 \(S'\) 的每一个前缀与每一个后缀,其 \(C\) 的数量都不小于 \(T\) 的数量。
\(N,Q\le 5\times 10^5\)
还不错的一道题,只不过考试的时候没来得及仔细想。
考虑怎么求出这个答案,设 \(C=1,T=-1\) ,我们可以从前往后扫前缀和 \(s\) ,如果 \(s\) 遇到 \(-1\) 变成负数了,那么一定要删掉当前的;做完这个操作之后再在当前删完的区间中做后缀和判断。
这样贪心一定是对的,因为前缀和是负数的话一定删最后面的,后缀和同理。
我们再进一步观察这个做法,发现删除的一个 \(-1\) 既可以在前缀被删除,也可以在后缀被删除,那么就相当于存在一个分解点,分界点前的最小前缀和是要删去的 \(-1\) 个数,分界点后的最小后缀和是要删除的 \(-1\) 个数(取绝对值),并且要最大化。那么就会有下面这个形式化的式子:
\[ans=\max_{i=l}^{r}(\max_{j=l}^{i-1}(s_{l-1}-s_j)+\max_{k=i+1}^r(s_k-s_r))
\]
化简可以得到:
\[ans=(\max_{k=l+1}^r\max_{j=k}^rs_k-s_j)-(s_r-s_{l-1})
\]
发现前面就是一个最大子段和,各种数据结构维护都可以。
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
const int N = 5e5;
using namespace std;
int n,q,s[N + 5];
char ch[N + 5];
struct node
{
int pre,suf,sm,ans;
};
node operator +(node a,node b)
{
a.ans = max(a.ans,b.ans);
a.ans = max(a.ans,a.suf + b.pre);
a.suf = max(a.suf + b.sm,b.suf);
a.pre = max(a.pre,a.sm + b.pre);
a.sm += b.sm;
return a;
}
struct Seg
{
node s[N * 4 + 5];
#define zrt k << 1
#define yrt k << 1 | 1
void build(int k,int l,int r)
{
if (l == r)
{
if (ch[l] == 'C')
s[k].pre = s[k].suf = s[k].sm = s[k].ans = 1;
else
s[k].sm = -1;
return;
}
int mid = l + r >> 1;
build(zrt,l,mid);
build(yrt,mid + 1,r);
s[k] = s[zrt] + s[yrt];
}
node query(int k,int l,int r,int x,int y)
{
if (l >= x && r <= y)
return s[k];
int mid = l + r >> 1;
if (x > mid)
return query(yrt,mid + 1,r,x,y);
if (y <= mid)
return query(zrt,l,mid,x,y);
return query(zrt,l,mid,x,y) + query(yrt,mid + 1,r,x,y);
}
}tree;
int main()
{
//freopen("elections.in","r",stdin);
//freopen("elections.out","w",stdout);
scanf("%d",&n);
scanf("%s",ch + 1);
for (int i = 1;i <= n;i++)
{
s[i] = s[i - 1];
if (ch[i] == 'C')
s[i]++;
else
s[i]--;
}
tree.build(1,1,n);
int l,r;
scanf("%d",&q);
while (q--)
{
scanf("%d%d",&l,&r);
printf("%d\n",tree.query(1,1,n,l,r).ans - (s[r] - s[l - 1]));
}
return 0;
}