空间复杂度
空间复杂度就是你的程序占用了多少空间。
一个 int
类型的范围一般是
所以一般情况下,int
变量,大约三千万。
每道题都有一定的内存限制,注意数组的开设大小。
如果你开的空间超过了内存限制,会爆出 MLE
错误,表示内存超限。
如果你访问到了没有定义的数组范围,比如定义了 a[100]
而访问了 a[110]
,那么会爆 RE
错误,表示访问越界。
时间复杂度
我们的计算机一秒大约能跑五千万次运算。
什么是一次运算?
a=b+c;
就算一次运算。
所以大额时间复杂度的来源主要在于循环和递归,一般手写是绝对写不到五千万的。
时间复杂度一般采用大
for(int i=1;i<=n;++i)
{
cin>>a[i];
}
2
3
4
这个循环执行了
for(int i=1;i<=n;++i)
{
cin>>a[i];
a[i]*=2;
}
2
3
4
5
这个循环执行了
因为
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
cin>>a[i][j];
}
}
2
3
4
5
6
7
这个时间复杂度是
for(int i=1;i<=n;++i)
{
for(int j=1;j<=100;++j)
{
cin>>a[i][j];
}
}
2
3
4
5
6
7
这个时间复杂度是
为什么这个
考虑到我们一秒只能跑
for(int i=1;i<=n;++i)
{
for(int j=1;j<=n;++j)
{
for(int k=1;k<=n;++k)
{
cin>>a[i][j][k];
}
}
for(int p=1;p<=2*n;++p)
{
++ans;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
这份的时间复杂度是
int n;cin>>n;
int sum=0;
while(n) n/=2,++sum;
2
3
这个时间复杂度是
不写底数是因为,从时间复杂度的角度来看,底数的大小(只要大于等于
如果严谨一点,也可以指出是
如果你的算法运行时间过长,会爆 TLE
错误,表示运行超时。
枚举
不同的枚举方式往往花费的时间也不同。
例一:
给定长度为 a[1]~a[n]
,有多少对不同的数字a[i]+a[j]==0
?
#include<bits/stdc++.h>
using namespace std;
int a[1010];
int main()
{
int n;cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
}
2
3
4
5
6
7
8
先做一点准备工作,读取一个长度为
然后最朴素的方法是:
for(int i=1;i<=n;++i)
{
for(int j=1;j<=n;++j)
{
if(i==j) continue;
if(a[i]+a[j]==0) ++ans;
}
}
2
3
4
5
6
7
8
这样是
考虑到如果 a[i]+a[j]==0
,那么对称地 a[j]+a[i]==0
,所以可以先算
for(int i=1;i<=n;++i)
{
for(int j=i+1;j<=n;++j)
{
if(a[i]+a[j]==0) ++ans;
}
}
ans=ans*2;
2
3
4
5
6
7
8
这样时间复杂度变成了
虽然理论复杂度没变,但实际上快了不少。
然而考虑到数字的范围不大,还可以用数组存储每个数字出现过多少次:
for(int i=1;i<=n;++i)
{
a[i]+=1000;//消除负数,把问题变成求有多少对数字和等于2000
}
//定义cnt[i]表示数字 i 出现过多少次
for(int i=1;i<=n;++i)
{
int t=2000-a[i];//另一个数字的大小应该是多少
if(t>=0&&t<=2000) ans+=cnt[t];
//前面的判断防止访问越界
}
ans=ans*2;
2
3
4
5
6
7
8
9
10
11
12
这样时间复杂度就变成了
例二:
求 1~n
每个数字分别有多少个因数。
暴力做法:
for(int x=1;x<=n;++x)
{
for(int i=1;i<=x;++i)
{
if(x%i==0) ++ans[x];
}
}
2
3
4
5
6
7
这样复杂度就是
考虑一个性质:如果
证明:如果
所以枚举因数的时候只需要枚举小于等于
for(int x=1;x<=n;++x)
{
for(int i=1;i*i<=x;++i)
{
if(x%i==0) ++ans[x];
if(i*i!=x) ++ans[x];//如果不是刚好根号,则对称的还有一个因数
}
}
2
3
4
5
6
7
8
这样每个数字只要枚举到根号,总时间复杂度
还有一个枚举思路:枚举一个数字,然后判断它是多少个数字的因子:
for(int i=1;i<=n;++i)
{
for(int x=i;x<=n;x+=i)
{
++ans[x];
}
}
2
3
4
5
6
7
虽然还不知道这样的时间复杂度是多少,但是显然上面两种枚举方法中,有些循环到的情况是不产生贡献的(不满足判断条件),而最后这种枚举方法每个循环到的地方都产生了贡献,效率应该是更高的。
这样的循环次数是
后面这个式子被称为调和级数,当
可以用积分近似计数该式子的值:
所以总体复杂度约等于
调和级数是算法中降低复杂度常用的枚举技巧,一定要学会。
简单来说就是先枚举数字,再枚举它的因子变成先枚举因子,再枚举倍数。
例三:
P1217 [USACO1.5] 回文质数 Prime Palindromes
因为
写一个程序来找出范围
如果按如下方式:
for(int i=a;i<=b;++i)
{
if(isprime(i)&&isreverse(i)) ++sum;
}
2
3
4
我们默认判断一个数是不是质数的时间复杂度是
那么这份代码时间复杂度就是
粗算一下,
显然大于
优化以下算法,我们枚举一个数字的前一半,然后根据它回文的性质,去生成后一半。
for(int i=1;i<10000;++i)
{
//以i为前一半,生成完整的数字x
if(isprime(x)) ++sum;
}
2
3
4
5
这样时间复杂度就变成了
粗算:
然而因为并不是每次都对最大值