1.打表
打表是一种典型的空间换时间的技巧,一般指将所有可能需要用到的结果事先计算出来,这样后面需要用到时就可以直接查表获得。打表常见的用法有如下几种:
在程序中一次性计算出所有需要用到的结果,之后的查询直接取这些结果;
在程序B中分一次或多次计算出所有需要用到的结果,手工把结果写在程序A的数组中,然后在程序A中就可以直接使用这些结果;
对一些感觉不会做的题目,先用暴力程序计算小范围数据的结果,然后找规律,或许就能发现一些蛛丝马迹。
2.活用递推
1040 有几个PAT (25 分)
字符串 APPAPT 中包含了两个单词 PAT,其中第一个 PAT 是第 2 位§,第 4 位(A),第 6 位(T);第二个 PAT 是第 3 位§,第 4 位(A),第 6 位(T)。
现给定字符串,问一共可以形成多少个 PAT?
输入格式:
输入只有一行,包含一个字符串,长度不超过10
5
,只包含 P、A、T 三种字母。
输出格式:
在一行中输出给定字符串中包含多少个 PAT。由于结果可能比较大,只输出对 1000000007 取余数的结果。
输入样例:
APPAPT
输出样例:
2
直接暴力会超时。换个角度,对一个确定位置的A来说,PAT的个数等于左侧的P个数乘以右侧的T个数,可以设定一个数组leftNumP记录每一位左侧P的个数。之后再设一个数rightNumT,从右向左遍历,碰到T时+1,碰到A时计算ans。
示例程序
const int maxn = 100;
const int MOD = 1000000007;
char str[maxn];//字符串
int leftNumP[maxn] = { 0 };//每一位左边含P的个数
int main()
{
gets_s(str);//读字符串
int len = strlen(str);//计算长度
for (int i = 0; i < len; i++)
{
if (i > 0)//如果不是第零位
{
leftNumP[i] = leftNumP[i - 1];//继承上一位的结果
}
if (str[i] == 'P')
{
leftNumP[i]++;//如果这一位是P再加1
}
}
int ans = 0, rightNumT = 0;
for (int i = len - 1; i >= 0; i--)//从末位向前计数
{
if (str[i] == 'T')
{
rightNumT++;//如果当前位是T,则加1
}
else if (str[i] == 'A')
{
ans = (ans + leftNumP[i] * rightNumT) % MOD;//如果碰到A则计算ans
}
}
printf("%d\n", ans);
return 0;
}
输出
3.随机选择算法
如何从一个无序的数组中求出第K大的数?最直接的想法是对数组拍一下序,但这样的做法需要O(nlogn)的时间复杂度,而随机选择算法对任何输入都可以达到O(n)的复杂度。
随机选择算法原理类似于随即快速排序算法。当A[left,right]执行一次randPartition的函数后,主元左侧的元素个数是确定的,且他们都小于主元。假设此时主元是A[p],那么A[p]就是A[left,right]第p-left+1大的数。不妨令M表示p-left+1,那么若K==M,说明第K大的数就是主元A[p];如果K<M成立,就说明第K大的数在主元左侧,往左递归即可,反之向右递归,以left=right作为递归边界,返回A[left]
//随机选择算法
void swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
int randPartition(int A[], int left, int right)
{
int p = (round(1.0 * rand() / RAND_MAX * (right - left) + left));
swap(A[p], A[left]);
int temp = A[left];//将A[left]存入临时变量temp
while (left < right)//left与right不相遇
{
while (left<right && A[right]>temp)right--;//不断左移right直到找到一个小于选定值的值
A[left] = A[right];//将A[right]移到A[left]
while (left < right && A[left] <= temp)left++;//不断右移left直到找到一个大于等于选定值的值
A[right] = A[left];//将A[left]移到A[right]
}
A[left] = temp;//temp归位
return left;//返回相遇的下标
}
//随机选择找第k大的数
int randSelect(int A[], int left, int right, int K)
{
if (left == right)return A[left];
int p = randPartition(A, left, right);
int M = p - left + 1;
if (K == M)return A[p];
if (K < M)
{
return randSelect(A, left, p - 1, K);
}
else
{
return randSelect(A, p + 1, right, K - M);
}
}
int main()
{
int a[] = { 5,12,7,2,9,3 };
int ans=randSelect(a, 0, 5, 3);
printf("%d\n", ans);
return 0;
}
运行结果
给定一个由整数组成的集合,集合中的整数各不相同,现在要将它分为两个子集合,使得这两个子集合的并为原集合、交为空集,同时在两个子集合的元素个数n1与n2之差的绝对值|n1-n2|尽可能小的前提下,要求它们各自的元素之和S1与S2之差的绝对值|S1-S2|大
可以使用上面用到的随机选择算法,本质就是找第n/2大的元素
//随机选择算法
void swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
int randPartition(int A[], int left, int right)
{
int p = (round(1.0 * rand() / RAND_MAX * (right - left) + left));
swap(A[p], A[left]);
int temp = A[left];//将A[left]存入临时变量temp
while (left < right)//left与right不相遇
{
while (left<right && A[right]>temp)right--;//不断左移right直到找到一个小于选定值的值
A[left] = A[right];//将A[right]移到A[left]
while (left < right && A[left] <= temp)left++;//不断右移left直到找到一个大于等于选定值的值
A[right] = A[left];//将A[left]移到A[right]
}
A[left] = temp;//temp归位
return left;//返回相遇的下标
}
//随机选择找第k大的数
int randSelect(int A[], int left, int right, int K)
{
if (left == right)return A[left];
int p = randPartition(A, left, right);
int M = p - left + 1;
if (K == M)return A[p];
if (K < M)
{
return randSelect(A, left, p - 1, K);
}
else
{
return randSelect(A, p + 1, right, K - M);
}
}
const int maxn = 100010;
int A[maxn], n;
int main()
{
srand((unsigned)time(NULL));
int sum = 0, sum1 = 0;
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
scanf("%d", &A[i]);
sum += A[i];
}
randSelect(A, 0, n - 1, n / 2);
for (int i = 0; i < n / 2; i++)
{
sum1 += A[i];
}
printf("子集1为:\n");
for (int i = 0; i < n / 2; i++)
{
printf("%d ", A[i]);
}
printf("\n子集2为:\n");
for (int i = n/2; i < n; i++)
{
printf("%d ", A[i]);
}
printf("\n|S1-S2|=%d\n", (sum - sum1) - sum1);
return 0;
}
运行结果