看到别人在群中讨论一道题,发现自己也不会做。后来才听到要用前缀和的方法进行求解。在此记录一下我所学到的知识。
题目内容
题目分析
可能遇到这种问题,每个人首先想到的办法就是暴力求解,(假设a小于b)即算出从a到b每个数中1的个数,然后求和。像这样:
#include<iostream>
using namespace std;
//cal函数求a中所包含的1的个数
int cal(int a)
{
int cnt = 0;
while(a)
{
if(a%10 == 1) cnt++;
a /= 10;
}
return cnt;
}
int main()
{
int a,b;
while(scanf("%d%d",&a,&b) != EOF)
{
if(a>b)
{
int tmp = a;
a = b;
b = tmp;
}
int sum = 0;
for(int i=a; i<=b; i++)
{
sum += cal(i);
}
printf("%d\n",sum);
}
}
但是这样的话会超时(Time Limit Exceed)。为什么呢?因为我们可以注意题目中的数据的范围:多组数据(不超过100000组),每组数据2个整数a,b.(1≤a,b≤1000000)。假设组数为T,a、b范围为N,计算每个数字中1的个数需要M次运算。那么时间复杂度就是O(T*N*M),T和N是很大的,T为1e5,N为1e6,显然这样是会超时的。
那么该如何处理呢?
这里就运用到了一种前缀和的思想。具体就是用一个全局数组sum[],用sum[i]来保存前i个数中所包含的1出现的次数之和 。而且提前算好sum[0]-sum[1e6]的值。那么每组数据就可以直接用sum[b]-sum[a-1]来计算。时间复杂度就是
O(1*T)+O(N*M) = O(N*M)
这样就不会超时。
我的代码
#include<iostream>
using namespace std;
const int maxn = 1e6+1;
int sum[maxn]; //sum[i]表示前i个数中所包含的1出现的次数之和
//cal函数求a中所包含的1的个数
int cal(int a)
{
int cnt = 0;
while(a)
{
if(a%10 == 1) cnt++;
a /= 10;
}
return cnt;
}
int main()
{
//求sum[0]-sum[1e6]的值
sum[0] = 0;
for(int i=1; i<=1e6; i++)
{
sum[i] = sum[i-1] + cal(i);
}
int a,b;
while(scanf("%d%d",&a,&b) != EOF)
{
if(a>b)
{
int tmp = a;
a = b;
b = tmp;
}
printf("%d\n",sum[b]-sum[a-1]);
}
return 0;
}
再看前缀和
我觉得前缀和的思想就是一种预处理,是一种以空间换时间的做法。就是先提前求得sum[i],保存下来,以后计算的时候直接用。动态规划就经常用到这样的手段。