有时候在竞赛中我们可能会碰到一种比较棘手的题目,这种题目数据较大且运算量较大,如果直接写解可能会致使时间复杂度变得很大,少则O(n2),多则O(nn),于是问题就来了,如何设计高效解法。但是在设计高效算法之前我们一般会考虑暴力求解,今天介绍一种方法——打表。什么是打表呢?打表按个人理解就是将要用到的尽可能多的数据先储存起来,在需要时再查询调用,查询调用的复杂度一般为O(C)(C for costant),比如说枚举阶乘,运算量及大,那么就可以直接打表储存。但是哲学观点告诉我们,这样的方法是有代价的,而打表的代价就是内存会非常非常大。不过联赛的内存限制一般是128M,是可以打表的。但一些高级的比赛会有spj(特判,如内存限制,长度限制,数据限制之类),因此打表只是一种就急的办法,平时做题最好还是考虑高效算法。
下面举一个小例子——打素数表:
一般来说我们判断素数的方法是使用谓词函数is_prime(详见LRJ《算法竞赛入门经典》)
1 int is_prime(int n) 2 { 3 if(n<2)return 0; 4 for(int i=2;i<=sqrt(n);++i) 5 if(n%i==0)return 0; 6 return 1; 7 }
这种方法复杂度O(n),不过题目中加了条件需要枚举的话一般是O(n2)。
打一个素数表:
#define maxn 1000000 bool v[maxn]; int prime[maxn]; void input_prime(int n) { memset(v,true,sizeof(v)); int num = 0; for(int i=2;i<=n;++i) { if(v[i]==true) { num++; prime[num]=i; } for(int j=1;((j<=num)&&(i*prime[j]<=n));++j) { v[i*prime[j]] = false; if(i%prime[j]==0)break; } } }
素数表就打好了,不过当数据比较小的时候两种算法复杂度是差不多的甚至筛法判定要容易,由数学知识易得:
f(x)=x2(x>0)与g(x)=C(C>0)由导数知识可知在(0,√c)时g(x)的变化率是大于f(x)的,在(√c,+∞)时f(x)变化率大于g(x)。
再拿一道题目来看看——某次苦逼的信息组考试题之人赢传说:
题目概况如下:看来是可以打表的,但是学长为了防止我们打表特意加了spj TAT。。。
题目描述如下:
乍一看这题目貌似很简单的样子,可以考虑维护(这个词看起来很有b格)一个递推数列,然后注意用一点快速幂的思想来取模即可,但看看这数据范围就吓尿了,
反正我是直接递推做的,只过了几个点。
显然这种数据范围真是太好打表了。。
这里还是把BBG学长的标准解法贴上来——
以及其std:
void numqueue() { a%=MOD;b%=MOD;c%=MOD;n%=MOD; x=1;y=n; z=((((n*n)%MOD+n)%MOD)*5004)%MOD; p=(((2*(n*n)%MOD)*n)%MOD); q=((3*n*n)%MOD); w=(((p+q+n)%MOD)*1668)%MOD; ans=(((a*x)%MOD)+((b*y)%MOD)+((c*z)%MOD)+((d*w)%MOD))%MOD; }
所谓的乘法逆元就体现在中间的对含除号的取模上。