题目链接在此。
和《算法笔记》内容稍稍不同,下次记录《算法笔记》中的表达。
来了,今天用《算法笔记》的方法实现了一遍,便于理解和总结“打表+递推将时间复杂度减少到O(N)法”的说法(我自己编的……)。
ps:思路一相关是上次的,思路二是《算法笔记》上过来的。
思路
思路1
暴力肯定是要超时的。然而对于一个确定的A来数,以它形成PAT的个数是这个A前面的P的个数以及这个A后面的T的个数的乘积。于是只需要知道所有A之前P的个数以及所有A之后T的个数,然后对于每个A进行前P后T个数乘积累加起来就是答案。
至于如何操作,用numP数组保存每个A之前的P的总个数,即numP[i] = j表示第i个A之前的P的个数为j个;用numT[i]表示第i+1个A(或是输入字符串末端)之前的所有T的个数,用t表示总共有t个A的话,numT[t]-numT[i]求的就是第i个A之后P的个数了。
思路二
思路一真的是真真实实的用’A’来拆分了输入的字符串啊~其实有更加通用的方法,那就是记录每一位左边’P’的个数以及右边’T’的个数(包括自身这一位)。
有了思路一的基础,直接看代码吧。
看完和这一题对照,会发现这个方法无比的好用。之前自己叫它们“用数组表示前n为和”,其实归根结底还是“递推”的思想,现在终于有了更贴切的名字——“打表+递推将时间复杂度减少到O(N)法”。
AC代码
思路一代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char str[100010]; //保存读入的字符串
long long numP[100010] = {0}, p = 0; //numP[i]表示第i个A之前'P'的个数,p为numP下标
long long numT[100010] = {0}, t = 0; //numT[i]表示第i+1个A之前'T'的个数,t为numT下标
long long sum = 0; //保存结果
int main(){
gets(str);
int len = strlen(str);
int count = 0; //记录A的个数
for(int i = 0; i < len; i++){
if(str[i] == 'P'){ //如果str[i] == 'P'
numP[p]++; //第p个A之前的'P'的数目+1
}else if(str[i] == 'T'){ //如果str[i] == 'T'
numT[t]++; //第t+1个A之前的'T'的数目+1
}else{ //str[i] == 'A'
count++; // A的个数+1
p++; //p+1,开始记录下一个A之前P的个数了
t++; //t+1, 开始记录下一个A之前T的个数了
numP[p] += numP[p-1]; //累加之前的P的个数
numT[t] += numT[t-1]; //累加之前的T的个数
}
}
for(int j = 0 ; j < count ; j++){ //有多少个A算多少种情况
sum += (numP[j] * (numT[t]-numT[j])); // numP[j]表示第j个A之前有多少个,numT[t]-numT[j] 表示第j个A之后有多少个T
}
printf("%lld\n",sum%1000000007);
return 0;
}
思路二代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char str[100010]; //保存输入字符串
int leftP[100010], rightT[100010]; //leftP[i]表示i左侧p的个数,rightT[i]表示i右侧T的个数
int main(){
gets(str);
int n = strlen(str);
//填充leftP
for(int i = 0; i <= n; i++){
if(i > 0){ //不是首位
leftP[i] = leftP[i-1]; //继承上一位的结果
}
if(str[i] == 'P'){ //这一位是'P'
leftP[i]++; //leftP[i]++
}
}
//填充rightT
for(int i = n-1; i >= 0; i--){
if(i < n-1){ //不是最后一位
rightT[i] = rightT[i+1]; //继承前一位的结果
}
if(str[i] == 'T'){ //如果当前位为'T'
rightT[i]++; //rightT[i]+1
}
}
//计算结果并输出
long long sum = 0; //保存结果
for(int i = 0; i < n; i++){
if(str[i] == 'A'){ //如果当前为是A
sum += ((leftP[i] * rightT[i])); //则sum加上当前为前面P的个数和后面T的个数的乘积
}
}
printf("%d\n",sum%1000000007); //记得模完输出
return 0;
}
当然,也可以不填充rightT,直接在找rightT的过程中累加答案:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char str[100010]; //保存输入字符串
int leftP[100010], rightT[100010]; //leftP[i]表示i左侧p的个数,rightT[i]表示i右侧T的个数
int main(){
gets(str);
int n = strlen(str);
//填充leftP
for(int i = 0; i <= n; i++){
if(i > 0){ //不是首位
leftP[i] = leftP[i-1]; //继承上一位的结果
}
if(str[i] == 'P'){ //这一位是'P'
leftP[i]++; //leftP[i]++
}
}
int sum = 0;
int rightT = 0;
for(int i = n-1; i >= 0; i--){
if(str[i] == 'T'){
rightT++;
}else if(str[i] == 'A'){
sum = (sum + (rightT*leftP[i]))%1000000007;
}
}
printf("%d\n",sum);
return 0;
}