小红的red计数
链接:https://ac.nowcoder.com/acm/contest/91592/E 来源:牛客网
1. 题目描述
小红拿到了一个字符串。她有若干次询问,每次询问:若翻转第l个字符到第r个字符对应的区间,该字符串有多少"red"子序列。 子序列指按原串顺序取若干字母(可以不连续)形成的新字符串。如"rreedd"存在8个"red"子序列。 请注意,每次询问后并不会真正翻转区间!
1.1 输入描述:
第一行输入两个正整数 代表字符串长度和询问次数。
第二行输入一个长度为
的、仅由小写字母组成的字符串。
接下来的
行,每行输入两个正整数
,代表一次询问。
,
1.2 输出描述:
输出q行,每行输出一个整数,代表询问的答案。
1.3 样例输入
6 2
rreedd
1 2
1 6
1.4 样例输出
8
0
2. 思路分析:
2.1 标签: 模拟 + 前缀和
2.2 解题思路
看到题目 次询问,且
最大为
,首先最基本的判断就是
slove()
算法的复杂度一定在 以内,但是
的最大为
,
slove()
的算法最多为 ,所以符合条件的算法并不多。(算法复杂度与时间的关系)
先想到本题的暴力做法,即通过 for()
判断每个 的左边有多少个
、右边有多少个
,通过从左往右预处理
的前缀和、从右往左预处理
的后缀和,可以把一般
的
slove()
优化为 的复杂度。
接下来考虑一般情况下答案的组成:
设 的前缀和为
( 遍历到
前面有多少个
),
的后缀和为
(遍历到
后面有多少个
),字符串
。其中,
~
中的
的贡献
和
~
中
的贡献
并没有变,只需要重新计算
~
中
的贡献再与前者相加即可,答案的正确性是显然的。
设 的坐标为
, 要计算
~
中的贡献。对于
~
的每个
,很显然都有
的贡献。
左边的
可拆为
和
(请注意
、
的位置实际并没有改变);
右边的
可拆为
和
。因此对于
~
中的
,共有
的贡献,因此我们可以得到如下的式子:
根据以上的式子,我们可以得到 的做法:
while(q--)
{
ll ans = 0;
for(int i = 1; i <= n; i++)
{
if(i < l || i > r) ans += pre[i] * suf[i];
else ans += (pre[l - 1] + (pre[r] - pre[i]) * (suf[r + 1] + (suf[l] - suf[i]))
}
}
比赛时我也只想到这里,很显然是过不了的,拿到了 20% 的分。 不够,我们要考虑
的复杂度,也就是考虑答案能由一个公式直接推出来,这一点跟牛客之前的也是考察前缀和的 [奏绝](E-奏绝_牛客小白月赛93 (nowcoder.com)) 如出一辙。
-
对
,我们考虑预处理:用一个
维护
数量的前缀和(表示遍历到
时
的数量),显然上式可以
地表示为
,不可能更简。
-
对
化简:
设定值
,则有:
对该式的三个部分进行分析:
-
就是
~
中
的数量乘以定值
、
,至于
的数量也可以用一个前缀和
来表示(遍历到
时左边
的数量)。
-
对于
,我们可以发现竟然和
和
中的式子一模一样,而我们之前用了一个
来维护
,这个时候
=
。
-
这个式子是最难想到的点。化简
,对于
,等式有贡献为
==设
为
的前缀和、
为
的前缀和,也就是 前缀和的前缀和==,其计算同样在预处理函数中进行。那么
=
,也可以
地算出来。
-
综上,我们可以得到:
最后,在预处理中按顺序处理 和
、
、
、
和
,最后通过上述的式子
地递推出答案即可,总复杂度
,算是前缀和中的难题了。
预处理程序:
void init()
{
for(int i = 1; i <= n; i++) //pre[]、E[]
{
if(str[i] == 'e') E[i] = E[i - 1] + 1;
else E[i] = E[i - 1];
if(str[i] == 'r') pre[i] = pre[i - 1] + 1;
else pre[i] = pre[i - 1];
}
for(int i = n; i >= 1; i--) //suf[]
if(str[i] == 'd') suf[i] = suf[i + 1] + 1;
else suf[i] = suf[i + 1];
for(int i = 1; i <= n; i++) //sum:未翻转时的red的数量
if(str[i] == 'e') sum[i] = sum[i - 1] + pre[i] * suf[i];
else sum[i] = sum[i - 1];
for(int i = 1; i <= n; i++) //ppre[]、psuf[]
if(str[i] == 'e')
{
ppre[i] = ppre[i - 1] + pre[i];
psuf[i] = psuf[i - 1] + suf[i] ;
}
else
{
ppre[i] = ppre[i - 1];
psuf[i] = psuf[i - 1];
}
}
3. AC Code
/*
前缀和 + 前缀和的前缀和
https://ac.nowcoder.com/acm/contest/91592/E
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int pre[N]; //前缀r的数量
int suf[N]; //后缀d的数量
int ppre[N]; //pre的前缀和,只有str[i]=='e'时增加
int psuf[N]; //suf的前缀和,只有str[i]=='e'时增加
int E[N]; //遍历i时前缀e的数量
ll sum[N]; //未翻转时的red的数量
int n;
string str;
void init()
{
for(int i = 1; i <= n; i++) //pre[]、E[]
{
if(str[i] == 'e') E[i] = E[i - 1] + 1;
else E[i] = E[i - 1];
if(str[i] == 'r') pre[i] = pre[i - 1] + 1;
else pre[i] = pre[i - 1];
}
for(int i = n; i >= 1; i--) //suf[]
if(str[i] == 'd') suf[i] = suf[i + 1] + 1;
else suf[i] = suf[i + 1];
for(int i = 1; i <= n; i++) //sum:未翻转时的red的数量
if(str[i] == 'e') sum[i] = sum[i - 1] + pre[i] * suf[i];
else sum[i] = sum[i - 1];
for(int i = 1; i <= n; i++) //ppre[]、psuf[]
if(str[i] == 'e')
{
ppre[i] = ppre[i - 1] + pre[i];
psuf[i] = psuf[i - 1] + suf[i] ;
}
else
{
ppre[i] = ppre[i - 1];
psuf[i] = psuf[i - 1];
}
}
void slove() //O(1)
{
int l,r; cin>>l>>r;
ll ans1 = sum[l - 1] + (sum[n] - sum[r]); //1~l-1和r+1~n的e的贡献,即非反转区间的e的贡献
ll x = pre[l - 1] + pre[r],y = suf[r + 1] + suf[l];
ll ans2 = x * y * (E[r] - E[l - 1]) - (x * (psuf[r] - psuf[l - 1]) + y * (ppre[r] - ppre[l - 1])) + (sum[r] - sum[l - 1]);
ll ans = ans1 + ans2;
cout<<ans<<endl;
}
int main()
{
int t = 1; //cin>>t;
cin>>n>>t;
cin>>str; str = "*" + str;
init();
while(t--)
{
slove();
}
return 0;
}