c语言 打表法,信息学竞赛中的算法技巧-打表法详解

原标题:信息学竞赛中的算法技巧-打表法详解

打表是算法竞赛中的一个技巧,这个技巧对于一些题目而言能够降低程序的运行时间,降低算法的时间复杂度。

《算法笔记》里面也有写到:举个例子,如果需要判断输入一个是哪几个素数相乘而得,例如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的拼法如图所示:

5a2b2a490a6038568cf9480a663827a9.png

注意:

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数学专题

责任编辑:

  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值