打表的研究
打表的研究
一.序言:打表,又名自欺欺人算法,属于应试技巧,然而并没有任何增长水平的意义2333(但毕竟我们考试可以得分嘛==),鉴于目前考试的类型和方式,打表还是很重要的。我试图把它总结一下。
二.所谓定义:就是暴力写出题目中要求求的东西,并存起来,然后查询复杂度就由o(???)变成了o(1),是不是很开心2333
然后是百度百科查到的定义:
打表还可以指出租车开计价器,你要求打表(打开计价器)的话,会显示出你所经过的里程和所应支付价格,对司机而言就意味着一张发票和费用;所以问你打不打表就是问你要不要发票。如果你和司机是商谈了打车的价格,比较便宜的话,一般是不会给你打表的。正常情况下你应该坚持打表,这样如果有意外事情发生,你可以通过发票追查到你乘坐过的车辆。
啊不不不,粘错了==
打表,是一信息学专用术语,意指对一些题目,通过打表技巧获得一个有序表或常量表,来执行程序某一部分,优化时间复杂度。这种算法也可用于在对某种题目没有最优解法时,用来得到分数的一种策略。
三.打表步骤
1.你要先写出一个暴力程序(无视时间复杂度,你开心就好),然后把它输出的结果存到一个文件里,如果可能的话,你也可以手算(手酸);
2.把它直接粘到你程序的一个数组里;
如果数太大了怎么办呢??你可以存这个数和前一个数之差,输出前缀和,或者是直接把数掰成几瓣存(然而这样有悖打表伦理==)
而且打表的话文件可能非常大哦,但一般没关系。
四.打表适用范围及例子
1.读入比较水
例(HAOI2015)FROM COGS
2267. [HAOI2016]放棋子
★★★ 输入文件:chess_2016.in 输出文件:chess_2016.out 简单对比
时间限制:1
s 内存限制:256 MB
【题目描述】
给你一个你N×N的矩阵,每行有一个障碍,数据保证任意两个障碍不在同一行,任意两个障碍不在同一列,要求你在这个矩阵上放N枚棋子(障碍的位置不能放棋子),要求你放N个棋子也满足每行只有一枚棋子,每列只有一枚棋子的限制,求有多少种方案。
【输入格式】
第一行一个N,接下来一个N×N的矩阵。
【输出格式】
一个整数,即合法的方案数。
【样例输入】
2
0 1
1 0
【样例输出】
1
【数据规模】
20%的数据保证: N<=10
60%的数据保证: N<=20
100%的数据保证: N<=200
【来源】
HAOI2016
粘的是不是很裸==(啊不管他)
然后讲打表思路:
分析题目会发现,本题和。。。(中国:韩国3:2。。伤心)
好啦继续,本题和后面棋子的站位并无关系,!!!没错我考场上就发现了!!但是。。我并没有相信这个事实。往后推没推出来规律(听Fancy说是错排公式)。没错,要推四步的话就能推出来(然而我只推了三步(′д` )…彡…彡)
每行每列有一个障碍,所以我们发现任意交换两行或者两列对答案没有影响,不妨把障碍交换到主对角线位置,就变成了语法百题的错排问题
设f[i]表示i个人的错排方案数
f[0]=1
f[1]=0
f[i]=(f[i-1]+f[i-2])*(i-1)
考虑地i个人怎么换,一种方法是与前i-1个人里的一个互换,这是f[i-2]*(i-1),另一种是拿第j个人的,并且第j个人不拿i的,在不考虑i的情况下第j个人不拿i与第j个人不拿j是等价的,所以就是f[i-1]*(i-1)
需要高精
具体做法及正解请看Fancy的题解
下面说打表方面,因为读入有意义的实际上就一个n,所以你可以直接手推啊==好吧还是写暴力吧。注意需要高精哦。
2.部分题目有规律或给了公式
例:
3. 好数
(good.cpp/c/pas)
【问题描述】
我们定义一个非负整数是“好数”,当且仅当它符合以下条件之一:
1. 这个数是0或1
2. 所有小于这个数且与它互质的正整数可以排成一个等差数列
例如,8就是一个好数,因为1,3,5,7排成了等差数列。
给出N个非负整数,然后进行如下三个操作:
1. 询问区间[L,R]有多少个好数
2. 将区间[L,R]内所有数对S取余(S≤1000000)
3. 将第C个数更改为X
提示:如果你不知道如何判断一个数是否为好数,你可以打个表找找规律。
【输入格式】
输入文件名为good.in。
第一行包含两个正整数N和M,M表示操作数目
第二行包含N个非负整数。
接下来的M行每行表示1个操作:“1 L R”表示第1个操作,“2 L
R S”表
示第2个操作,“3 C X”表示第3个操作。
【输出格式】
输出文件名为color.out。
对每个操作1,输出一个非负整数,表示区间内好数的个数。
【输入输出样例1】
good.in |
good.out |
3 6 |
2 0 2 2 |
【输入输出样例2】
good.in |
good.out |
8 5 |
3 6 4 |
【数据规模与约定】
样例点编号 |
N |
M |
N个数大小(≤) |
具有的操作 |
1,2 |
100 |
100 |
100 |
1,2,3 |
3,4 |
1000 |
1000 |
1000000 |
1,2,3 |
5,6,7 |
100000 |
100000 |
1000000 |
1,3 |
8,9,10 |
100000 |
100000 |
1000000 |
1,2,3 |
这个题我不讲了2333,主要说在好数的判断上,可以直接打表啊。
3.某些题的暴力可以用打表啊。。(暴力中的n值比较小)
把题目暴力分中的情况分类,然后暴搜。。
没错,像这样。(题就不粘了)
#include<iostream> #include<cstdio> using namespace std; int n,m; int x[300005],y[300005]; int nn[100005]; bool ans[300005]; int a; int r[305]; int main() { freopen("problem.in","r",stdin); freopen("problem.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%d%d",&x[i],&y[i]); } if(n==0||n==1) { cout<<"0";return 0; } if(n==2) { if(m==0) { cout<<"0";return 0; } if(m==1) { cout<<"0"<<endl<<"1";return 0; } if(m==2) { cout<<"1";return 0; } } if(n==3) { if(m==0) { cout<<"0";return 0; } if(m==1) { cout<<"0"<<endl<<"1";return 0; } for(nn[1]=1;nn[1]<=3;nn[1]++) { for(nn[2]=1;nn[2]<=3;nn[2]++) { for(nn[3]=1;nn[3]<=3;nn[3]++) { if(nn[1]!=nn[2]&&nn[1]!=nn[3]&&nn[2]!=nn[3]) { for(int i=1;i<=m;i++) { if(nn[x[i]]<nn[y[i]]) r[y[i]]=1; } for(int i=1;i<=305;i++) { a=a+r[i];r[i]=0; } ans[a]=true;a=0; } } } } for(int i=0;i<=m;i++) if(ans[i]==1) printf("%d\n",i); return 0; } if(n==4) { if(m==0) { cout<<"0";return 0; } if(m==1) { cout<<"0"<<endl<<"1";return 0; } for(nn[1]=1;nn[1]<=4;nn[1]++) for(nn[2]=1;nn[2]<=4;nn[2]++) for(nn[3]=1;nn[3]<=4;nn[3]++) for(nn[4]=1;nn[4]<=4;nn[4]++) if(nn[1]!=nn[2]&&nn[1]!=nn[3]&&nn[1]!=nn[4]&&nn[2]!=nn[3]&&nn[2]!=nn[4]&&nn[3]!=nn[4]) { for(int i=1;i<=m;i++) { if(nn[x[i]]<nn[y[i]]) r[y[i]]=1; } for(int i=1;i<=305;i++) { a=a+r[i];r[i]=0; } ans[a]=true;a=0; } for(int i=0;i<=m;i++) if(ans[i]==1) printf("%d\n",i); return 0; } if(n==5) { if(m==0) { cout<<"0";return 0; } if(m==1) { cout<<"0"<<endl<<"1";return 0; } for(nn[1]=1;nn[1]<=5;nn[1]++) for(nn[2]=1;nn[2]<=5;nn[2]++) for(nn[3]=1;nn[3]<=5;nn[3]++) for(nn[4]=1;nn[4]<=5;nn[4]++) for(nn[5]=1;nn[5]<=5;nn[5]++) if(nn[1]!=nn[2]&&nn[1]!=nn[3]&&nn[1]!=nn[4]&&nn[1]!=nn[5]&&nn[2]!=nn[3]&&nn[2]!=nn[4]&&nn[2]!=nn[5]&&nn[4]!=nn[3]&&nn[5]!=nn[3]&&nn[5]!=nn[4]) { for(int i=1;i<=m;i++) { if(nn[x[i]]<nn[y[i]]) r[y[i]]=1; } for(int i=1;i<=305;i++) { a=a+r[i];r[i]=0; } ans[a]=true;a=0; } for(int i=0;i<=m;i++) if(ans[i]==1) printf("%d\n",i); return 0; } if(n>5) { for(int i=0;i<=n-1;i++) printf("%d\n",i); } }
其他没什么了,毕竟是骗分,,
神犇请吐槽。2333