高效技巧与算法(1)

打表

利用空间换时间。

  • 在程序中一次性计算出所需要用到的结果,之后的查询直接取这些结果;
  • 在本地将题目所给范围内的结果跑出来后存给要提交的程序,这样查询起来时间消耗小;
  • 对一些不会解决的,先暴力破解小范围结果,再找规律观察。

活用递推

题目描述

在这里插入图片描述

思路

  直接暴力破解会超时,时间限制为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+mMOD
B = b + n ∗ M O D B = b + n * MOD B=b+nMOD

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) (AB)%MOD=(ab+anMOD+bmMOD+mnMODMOD)%MOD=(ab)%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题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

D-A-X

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值