打表
利用空间换时间。
- 在程序中一次性计算出所需要用到的结果,之后的查询直接取这些结果;
- 在本地将题目所给范围内的结果跑出来后存给要提交的程序,这样查询起来时间消耗小;
- 对一些不会解决的,先暴力破解小范围结果,再找规律观察。
活用递推
题目描述
思路
直接暴力破解会超时,时间限制为150ms。
对于确定位置的A来说,以它形成的PAT个数为左侧P个数与右侧T个数的乘积。
如何快速获得每一位左侧P的个数呢?定义数组leftNumP;从左向右遍历该字符串,若该i位为P,则leftNumP[i] = leftNumP[i - 1] + 1;若该i位非P,则leftNum[i] = leftNum[i - 1]。同理,从右向左遍历字符串,定义数组rightNumT来记录右侧T个数。
代码
#include<cstdio>
#include<cstring>
int main(){
char str[100001];
scanf("%s", &str);
int leftNumP[100000], rightNumT[100000];
int i, len;
len = strlen(str);
if(str[0] == 'P') leftNumP[0] = 1;
else leftNumP[0] = 0;
for(i = 1; i < len; i++){
if(str[i] == 'P') leftNumP[i] = leftNumP[i - 1] + 1;
else leftNumP[i] = leftNumP[i - 1];
}
if(str[len - 1] == 'T') rightNumT[len - 1] = 1;
else rightNumT[len - 1] = 0;
for(i = len - 2; i >= 0; i--){
if(str[i] == 'T') rightNumT[i] = rightNumT[i + 1] + 1;
else rightNumT[i] = rightNumT[i + 1];
}
int ans = 0;
const int MOD = 1000000007;
for(i = 0; i < len; i++){
if(str[i] == 'A')
ans = (ans + leftNumP[i] * rightNumT[i]) % MOD;
}
printf("%d\n", ans);
return 0;
}
注:先求和后取余与先取余后求和结果一致;先求积后取余与先取余后求积结果一致,证明如下:
A = a + m ∗ M O D A = a + m * MOD A=a+m∗MOD
B = b + n ∗ M O D B = b + n * MOD B=b+n∗MOD
和
∴ A % M O D + B % M O D = ( a + b ) % M O D = ( A + B ) % M O D A \% MOD + B \% MOD = (a + b) \% MOD = (A + B) \% MOD A%MOD+B%MOD=(a+b)%MOD=(A+B)%MOD
得证
积
∴ ( A ∗ B ) % M O D = ( a ∗ b + a ∗ n ∗ M O D + b ∗ m ∗ M O D + m ∗ n ∗ M O D ∗ M O D ) % M O D = ( a ∗ b ) % M O D = ( A % M O D ) ∗ ( B % M O D ) (A * B) \% MOD = (a * b + a* n* MOD + b * m * MOD + m * n * MOD *MOD) \% MOD = (a * b) \% MOD = (A \% MOD) * (B \% MOD) (A∗B)%MOD=(a∗b+a∗n∗MOD+b∗m∗MOD+m∗n∗MOD∗MOD)%MOD=(a∗b)%MOD=(A%MOD)∗(B%MOD)
得证
随机选择算法
讨论一类问题:给定一个无序数组,求其中第K大的数;例如,对数组{5,12,7,2,9,3},第三大的数为5,第五大的数为9。
显然,使用sort函数对其排序后可直接输出,但是时间复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),存在一种更好的方法使时间复杂度为
O
(
n
)
O(n)
O(n)。
随机选择算法来源于随机快排,当对A[left, right]进行一次randPartition运算后,A[p]左右侧元素个数确定,A[p]即为第p - left + 1大的数,若为K,则直接输出,否则在其左右侧查找。
代码如下:
//随机选择算法,从A[left, right]中返回第K大的值
int randSelect(int A[], int left, int right, int K){
if(left == right) return A[left]; //边界
int p = randPartition(A, left, right); //划分后主元位置为p
int M = p - left + 1; //A[p]是A[left, right]中第M大的数
if(K == M) return A[p]; //找到第K大的数
else if(K < M) //第K大的数在主元左侧
return randSelect(A, left, p - 1, K);
else //第K大的数在主元右侧
return randSelect(A, p + 1, right, K);
}
//对区间[left, right]进行划分
int Partition(int A[], int left, int right){
int temp = A[left]; //将A[left]存放至临时变量temp
while(left < right){
while(left < right && A[right] > temp) right--; //反复左移right
A[left] = A[right];
while(left < right && A[left] <= temp) left++; //反复右移left
A[right] = A[left];
}
A[left] = temp; //放置temp至left与right相遇的地方
return left; //返回相遇的下标
}
注:在Partition()函数执行一遍之后,主元左侧数均小于主元,右侧数均大于主元,以做好划分;根据这个特点可以完成数组的划分工作。
参考资料
[1]. 《算法笔记》P146-151
[2]. 快速排序算法实现
[3]. PAT B1040题