原标题:信息学竞赛中的算法技巧-打表法详解
打表是算法竞赛中的一个技巧,这个技巧对于一些题目而言能够降低程序的运行时间,降低算法的时间复杂度。
《算法笔记》里面也有写到:举个例子,如果需要判断输入一个是哪几个素数相乘而得,例如12 = 2*2*3,我们就需要判断哪些是素数,如果对于输入的数,我们有一个循环,在循环里面判断是不是素数,是,就看能不能整除输入的数,不能,就下一个;
这时候就有个问题,如果每次都在循环里面判断是否是素数,就是有O(M)的时间复杂度,再加上本身就在一个大循环内部,所以这样进行程序设计的时间复杂度就有O(N^2)。
为了降低时间复杂度,我们可以使用打表的技巧,在主程序外部我们先建立一个素数表,用于查询某个数是否是素数,之后再在程序的循环里面直接调用查询,时间复杂度为O(1)。
总结打表的几个优点:
① 在程序中一次性计算出需要的结果,之后直接查询,一般情况下,Q次查询的时间复杂度只需要O(n+Q);
② 对于一些题目,数据范围非常大,可以暴力计算小范围数据结果,然后找规律。
打表就是将所有输入情况的答案保存在代码中,输入数据后直接输出就可以了
打表法具有 快速,易行(可以写暴力枚举程序)的特点, 缺点是代码可能太大,或者情况覆盖不完
对于不会超时,数据规模适合打表,为了简洁你也可以打表
例一:NOIP2008T2
Problem Deion
给你n根火柴棍,你可以拼出多少个形如“A+B=C”的等式?等式中的A、B、C是用火柴棍拼出的整数(若该数非零,则最高位不能是0)。用火柴棍拼数字0-9的拼法如图所示:
注意:
1. 加号与等号各自需要两根火柴棍
2. 如果A≠B,则A+B=C与B+A=C视为不同的等式(A、B、C≥0)
3. n根火柴棍必须全部用上
Input
输入一个整数n(n≤24)。
Output
输出能拼成的不同等式的数目。
这道题n<=20完全可以打表,代码( 生成答案):
#include
#include
#include
using namespace std;
int main{
freopen("ans.txt","w",stdout);
int a[10]={6,2,5,5,4,5,6,3,7,6};
int i,j,temp=0,num=0,k,in[2020],n;
in[0]=6;
for(i=1;i<=2000;i++){
k=i;
temp=0;
while(k){
temp+=a[k%10];
k/=10;
}
in[i]=temp;
}
n=0;
Again:
num=0;
for(i=0;i<=999;i++){
for(j=0;j<=999;j++){
if(n==in[i]+in[j]+in[i+j]+4) num++;
}
}
printf("%d,",num);
if(n<24){n++;goto Again;}
return 0;
}
————————————————
提交代码:
#include
using namespace std;
int ans[]={0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,8,9,6,9,29,39,38,65,88,128};
int n;
int main{
cin>>n;
cout<
return 0;
}
是不是很简洁
例二:骨牌问题[3]
Deion
在《骨牌问题[版本1]》中问题描述为:“有 2 行 n 列的长方形,用 n 个 1*2 的骨牌铺满,请计算有多少种铺法。”我们知道这个问题可用递推算法来解决,递推方程如下:
d[1]=1;
d[2]=2;
d[n]=d[n-1]+d[n-2] (n>2)
现在我们把这个问题推广一下:“有 m 行 n 列的长方形,用 (m*n)/2 个 1*2 的骨牌铺满,请计算有多少种铺法。
Input
若干行,每行包含两个整数:m,n,表示长方形的行和列的数量。
Output
若干行,每行一个整数,对应输入中的长方形铺满的方案数。
Sample Input 1
1 2
1 3
1 4
2 2
2 3
2 4
2 11
4 11
Sample Output 1
1
0
1
2
3
5
144
51205
Hint
m*n<=121
由于目前主要目的是讲打表法,此题解法不重点说明,反正正解是用的状压DP
这道题m*n<=121完全可以用打表的方法,先状压DP生成表:
#include
using namespace std;
#define ll long long
int n,m,ALL;
ll d[121][2058];
int A,B;
void shift(int i,int j,int A_){
if(j>n-1){
d[i][B]=d[i][B]+d[i-1][A_];
return;
}
if(A_&(1<
B=B&(~(1<
}
if(j
B|=(1<
}
if(!(A&(1<
B|=(1<
}
}
void dp{
if(n>m) swap(n,m);
ALL=(1<
memset(d,0,sizeof(d));
d[0][ALL]=1;
for(int i=0;i
for(A=0;A<=ALL;A++){
B=0;
shift(i+1,0,A);
}
}
int main{
freopen("ans.txt","w",stdout);//输出到答案表中
int cnt=0;
printf("t");
for(int i=1;i<=121;i++)
for(int j=1;i*j<=121;j++) if(i*j%2==0&&i<=j){//枚举情况
n=i;m=j;
dp;//状压DP
cnt++;
printf("f[%d][%d]=%lldLL",i,j,d[m][ALL]);
printf("%s",cnt%3?",":";nt");//格式控制
}
return 0;
}
然后把答案copy到程序中就可以了:
#include
using namespace std;
#define ll long long
ll f[20][150];
int x,y;
void get_V{
f[1][2]=1LL,f[1][4]=1LL,f[1][6]=1LL;
f[1][8]=1LL,f[1][10]=1LL,f[1][12]=1LL;
f[1][14]=1LL,f[1][16]=1LL,f[1][18]=1LL;
f[1][20]=1LL,f[1][22]=1LL,f[1][24]=1LL;
f[1][26]=1LL,f[1][28]=1LL,f[1][30]=1LL;
f[1][32]=1LL,f[1][34]=1LL,f[1][36]=1LL;
f[1][38]=1LL,f[1][40]=1LL,f[1][42]=1LL;
f[1][44]=1LL,f[1][46]=1LL,f[1][48]=1LL;
f[1][50]=1LL,f[1][52]=1LL,f[1][54]=1LL;
f[1][56]=1LL,f[1][58]=1LL,f[1][60]=1LL;
f[1][62]=1LL,f[1][64]=1LL,f[1][66]=1LL;
f[1][68]=1LL,f[1][70]=1LL,f[1][72]=1LL;
f[1][74]=1LL,f[1][76]=1LL,f[1][78]=1LL;
f[1][80]=1LL,f[1][82]=1LL,f[1][84]=1LL;
f[1][86]=1LL,f[1][88]=1LL,f[1][90]=1LL;
f[1][92]=1LL,f[1][94]=1LL,f[1][96]=1LL;
f[1][98]=1LL,f[1][100]=1LL,f[1][102]=1LL;
f[1][104]=1LL,f[1][106]=1LL,f[1][108]=1LL;
f[1][110]=1LL,f[1][112]=1LL,f[1][114]=1LL;
f[1][116]=1LL,f[1][118]=1LL,f[1][120]=1LL;
f[2][2]=2LL,f[2][3]=3LL,f[2][4]=5LL;
f[2][5]=8LL,f[2][6]=13LL,f[2][7]=21LL;
f[2][8]=34LL,f[2][9]=55LL,f[2][10]=89LL;
f[2][11]=144LL,f[2][12]=233LL,f[2][13]=377LL;
f[2][14]=610LL,f[2][15]=987LL,f[2][16]=1597LL;
f[2][17]=2584LL,f[2][18]=4181LL,f[2][19]=6765LL;
f[2][20]=10946LL,f[2][21]=17711LL,f[2][22]=28657LL;
f[3][6]=41LL,f[3][8]=153LL,f[3][10]=571LL;
f[3][12]=2131LL,f[3][14]=7953LL,f[3][16]=29681LL;
f[4][4]=36LL,f[4][5]=95LL,f[4][6]=281LL;
f[4][7]=781LL,f[4][8]=2245LL,f[4][9]=6336LL;
}
int main{
get_V;
while(scanf("%d%d",&x,&y)==2){
if(x>y) swap(x,y);
printf("%lldn",f[x][y]);
}
return 0;
}
值得注意的是,long long赋值的时候建议加上LL后缀
还有,当m*n为奇数是由于全局变量默认为0,正好输出0
例三:
【训练题】错排问题
Deion
班里出了各淘气包,经常搞的老师哭笑不得。淘气包今天将班里每个同学的钥匙放在别人的橱柜里,这样每个人的橱柜里放着的都不是自己的钥匙。当然,也就都锁不上自己的橱柜了。
老师对淘气包说:“你在考验老师,那么老师也考考你,好不好?”淘气包跃跃欲试,老师的问题是:淘气包这种放置钥匙的方法,会有多少种不同的情况呢?
Input
一个整数n:橱柜的个数即不同钥匙的个数(n<=12)
Output
一个整数:不同的方案数。
Sample Input 1
3
Sample Output 1
2
Hint
n<=12
这道题其实是一个数学或动态规划(递推)题,面对n<=12的规模暴力排列生成超时,也可以打表
#include
#include
#include
using namespace std;
/*
bool judge(int w){
for(int i=1;i<=w;i++){
if(a[i]==i) return false;
}
return true;
}
*/
int main{
/*
递推方程分析:
1.分析:暴力枚举加打表
2.现在我会分析了:f(i)=(i-1)(f(i-1)+f(i-2))
方法:设两个递推方程,消去一个就可以了
f(n+1)=n*g(n) g(n)=(n-1)*g(n-1)+f(n-1)
*/
/*
for(int n=2;n<=12;n++){
ans[n]=0;
for(int i=1;i<=n;i++) a[i]=i;
while(next_permutation(a+1,a+n+1)){
if(judge(n)) ans[n]++;
}
printf("%d,",ans[n]);
}
*/
scanf("%d",&n);
printf("%dn",ans[n]);
return 0;
}
其他应用:
a.生成小范围的素数表,欧拉函数表
b.生成某些需要的常数
————————————————
以上版权声明:本文作者Hi_KER,感谢奉献好的文章给大家。
分块打表
“求[a,b]之间有多少个数满足balabalabala,b<=10^9”
单次Check很快然而枚举会T
计算前缀和,打表,打不下,怎么办?
10^9=10^3 * 10^6,把前缀和序列分块,一块10^3,打10^6,还是打得下的。
然后查询的时候找到最近的一个块端点,从块端点的下一个位置开始到查询的值为止枚举,复杂度O(Check*10^3)
CSP-J/S2020训练营课程体系
全国多个校区开课(包含在线课程)
C C++入门级,NOIP普及组,NOIP提高组,NOI级
课表
NOIP 普及组基础营
NOIP 普及组 精英营
NOIP 提高组储备营
NOIP 提高组 突破营
NOIP 提高组 腾飞营
NOIP&NOI数学专题
责任编辑: