1040 有几个PAT (25 point(s))

#include <bits/stdc++.h>
using namespace std;

int main() {
	string str;
	cin >> str;
	
	int cnt[2][str.size()]{0}, P = 0, T = 0, sum = 0;
	// 正序统计 P 字符个数
	for(int i = 0; i < str.size(); i++){
		if(str[i] == 'P') P++;
		cnt[0][i] = P;
	}
	
	// 逆序统计遍历 
	for(int i = str.size() - 1; i >= 0; i--){
		
		if(str[i] == 'T') T++;
		cnt[1][i] = T;
		
		// 如果是A 字符 计算并累加
		if(str[i] == 'A'){
			sum += cnt[0][i] * cnt[1][i];
			sum %= 1000000007;
		}
	}
	cout << sum;
}

sum %= 1000000007;

写完的时候过了样例,但是没有过最后两个测试点。但是印象中有没有什么需要处理的了。看了别人的代码后才发现忘了求余的操作。

所以发现有测试点没过的时候可以重新看看题目,看看有哪些条件是没有用到过的。

还有就是这个求余不能够在最后面结果的时候求余输出,因为很可能在某一次累加的时候,sum 就溢出了,所以得每一次都求余一次避免溢出。


因为之前这题目写过一次,但是不理解为什么可以这样写。这次再来思考一次。

题目的 “PAT” 的条件,字符不需要连续,只要在字符串任意位置找到这三个字符能够拼起来即可。

那么如果按照正常的想法来看,首先找到第一个 P ,在这个 P 的基础上进入子循环,再找下一个 A,类似的再去找 T。这就会构成三重循环,复杂度是O(n^3),看题目的时限 250ms 必然超时。

然后想怎么优化,前面的思路是建立三重循环来找,那么每一次找到都意味着需要从当前的字符位置往后遍历一次,这意味我们会经历大量的重复的循环,但是循环中可能没有干事情。

所以我们需要利用每一次循环中遍历到元素,并尝试利用遍历时候能否添加标记,以便不需要重复循环。

假如我们固定一个字符,比如首字符 A,那么我们就从前面的的,以 P 为首向后连续找两个字符,变成了,分别向前后找两个字符。

比如说有字符串 PPATAT,以 P 的话,那么先找到 A 然后在的 A 基础上再去找 T。比如这里第一个 P 的第一个 A 后面有两个 T。第二个 A 后面有一个 T。第二个 P 同理。即 (2 * 1 + 1 * 1) + (2 * 1 + 1 * 1) = 6

同样的例子如果以 A 固定的话,第一个 A 左边有两个 P 右边有两个 T,第二个 A 左边有两个 P 右边有一个 T。
(2 * 2) + (2 * 1)。

如果用代码来处理的话, P 就需要不断嵌入子循环去找后面的 A 或者 T,而固定 A 的话也需要从当前的字符开始向前后找字符。


那为什么固定 P 不能用标记字符个数的做法。

如果用 P 来表示,那么每一次的统计都必须基于 P 的基础上的 先找到 A 然后算 A 后面的 T 个数,再遍历找下一个 A 然后再算这个 A 后面的 T 的个数。这个 P 找完之后再下一个 P 又得进行同样的步骤。可以看到每一次 P 或者 A 循环改变之后 T 的数量都必然发生改变。

而如果固定 A, 那么同样的左边的 P 需要以当前位置统计一次,右边 T 同理。

所以说这两个如果没有什么其他方法介入根本没什么区别。不过就这样看,第一个 P 还是立方的复杂度,但是第二个可以变成一个循环嵌套两个线性循环。

这么看关键应该是遍历并标记个数的问题。

不过可以看到,前面计算的时候已经能够看出两个算法之间的区别了, 用 P 的话,累加之间还有进一步相乘和累加,但是如果用 A 相乘并累加就可以解决问题了。


当时想到的关键是标记的方式可以取代多次的循环,比如就正确答案所展示的,通过正反两次循环就可以标记运算时需要的数据,然后就可以减少大量重复但是低效的循环。

但是编不下去了,不明所以然,以后想到再说。

参考理解

posted on 2021-08-31 15:10  Atl212  阅读(25)  评论(0编辑  收藏  举报

导航