小红的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)) 如出一辙。

  • ,我们考虑预处理:用一个 维护 数量的前缀和(表示遍历到 的数量),显然上式可以 地表示为 ,不可能更简。

  • 化简:

    设定值 ,则有:

    对该式的三个部分进行分析:

    1. 就是 ~ 的数量乘以定值 ,至于 的数量也可以用一个前缀和 来表示(遍历到 时左边 的数量)。

    2. 对于 ,我们可以发现竟然和 中的式子一模一样,而我们之前用了一个 来维护 ,这个时候 =

    3. 这个式子是最难想到的点。化简 ,对于 ​​ ,等式有贡献为

      ==设 的前缀和、 的前缀和,也就是 前缀和的前缀和==,其计算同样在预处理函数中进行。那么 = ,也可以 地算出来。

​ 综上,我们可以得到:

​ 最后,在预处理中按顺序处理 ,最后通过上述的式子 地递推出答案即可,总复杂度 ,算是前缀和中的难题了。

​ 预处理程序:

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;
}

4.liwenliwen_