0.1 介绍
任何蒟蒻必须经过大量的刷题练习才能成为大牛乃至于神牛
这就是著名的 lzn 定理。然而,我们这些蒟蒻们,没有经过那么多历练,却要和大牛们同场竞技,我们该怎么以弱胜强呢?
答案就是:骗分
骗分是什么呢?骗分就是用不是正解的程序(保证我们能轻松搞定的程序),尽可能多得骗取分数(以便骗取 1=)。
接下来,就让我们走进这本大作《OI骗分导论》。
0.2 部分内容引用及参考
新版骗分导论 BY秋名山码民
骗分导论 BY李博杰
Part.1 从无解出发
1.1 无解情况
在很多题目中都有这句话:若无解,请输出-1
于是我们只要 cout<<-1; 就可。
如:文化之旅(P1024)
仅需输出 -1 就可得 2828 分
1.2 骗样例
每道题目的后面,都有一组“样例输入”和“样例输出”。它们的价值极大,不仅能初步帮你检验程序的对错(特别坑的样例除外),而且,如果你不会做这道题(这种情况蒟蒻们已经司空见惯了),你就可以直接输出样例!
如 USACO,每题输出样例就可得 11 个测试点的分。
Part.2 非完美的算法
2.1 暴力
暴力可以骗一些有规律的但你想不出来的题目。
例子:辗转相除法(P1015)
long long exgcd(long long x,long long y,long long cs){
if(y==0) return cs;
else return exgcd(y,x%y,cs+1);
}
int main(){
long long n,maxcs=0,ma=-1,mb=-1;
cin>>n;
for(long long a=1;a<=n;a++){
for(long long b=1;b<=a;b++){
long long ccs=exgcd(a,b,0);
if(ccs>maxcs){
maxcs=ccs;
ma=a;
mb=b;
}
}
}
cout<<ma<<" "<<mb<<" "<<maxcs;
return 0;
}
2.2 DFS
这对于你的骗分是至关重要的。比如说,一些动态规划题,可以DFSDFS;数学题,可以 DFSDFS;剪枝的题,更能 DFSDFS。下面以一道 NOIP 题为例,解释一下 DFSDFS 骗分。
采药(P1025)
void dfs(int depth,int l,int s){
if(l<0) return;
if(depth>m){ans=max(ans,s);return;}
dfs(depth+1,l-sj[depth],s+jz[depth]);
dfs(depth+1,l,s);
}
Part.3 打表
暴力出奇迹,打表进省一
3.1 小数据——交表
小数据我们可以直接打表,将表交上去即可;大的数据也可以,我们选其中的小数据打,至于大数据,就看时间吧。
如:旋转数塔(P1026)
if(n==1) ···
else if(n==2) ···
else if(n==3) ···
else if(n==4) ···
else if(n==7) ···
else ···
为什么 44 后面直接跳到了 77 ?
我们来看看数据点:
本题共有 1010 个测试点,每个测试点 1010 分
对于测试点 11 :n=1n=1;
对于测试点 22 :n=2n=2;
对于测试点 33 :n=3n=3;
对于测试点 44 :n=4n=4;
对于测试点 55 :n=7n=7;
对于测试点 66 :n=8n=8;
对于测试点 77 :n=10n=10;
对于测试点 88 :n=15n=15;
对于测试点 99 :n=25n=25;
对于测试点 1010:n=50n=50。
数据没有 55 ~ 66,我们当然不要打,这节省了时间。
由此可见,打表还是要有方法的。
3.2 提交答案题——枚举
对于提交答案的题,干脆就枚举一下,然后把结果交上去。
如:三连击(P1027)
枚举程序:
int main(){
for(int i=192;i<=333;i++){
int i1=i,i2=i*2,i3=i*3;
int i1g=i1%10,i1s=i1/10%10,i1b=i1/100;
int i2g=i2%10,i2s=i2/10%10,i2b=i2/100;
int i3g=i3%10,i3s=i3/10%10,i3b=i3/100;
if(i1g+i2g+i3g+i1s+i2s+i3s+i1b+i2b+i3b==45 && i1g*i2g*i3g*i1s*i2s*i3s*i1b*i2b*i3b==362880) cout<<i1<<" "<<i2<<" "<<i3<<endl;
}
return 0;
}
运行结果为:
192 384 576
219 438 657
273 546 819
327 654 981
于是我们只需将这个提交就可以了(虽说这个不会超时,但遇到会超时的可以这么做)。
3.3 大数据——预置
有些题目数据很大,直接暴力会超时,我们不如先内置几个部分。
如:数页码(P1028)
每隔 一千万 打一次表,打表程序:
long long sum(int i){//计算一个数字各数位上数字的和
long long ret=0;
while(i) ret+=i%10,i/=10;
return ret;
}
long long f(int l,int r){//计算[l,r]区间内所有数字sum值的和
long long ret=0;
for (int i=l;i<=r;i++) ret+=sum(i);
return ret;
}
int main(){
for(int i=1;i<1000000000;i+=10000000)
printf("%d,",f(i,i+9999999));//每隔一千万打一个表
return 0;
}
然后,把这个表复制到正解的一个数组中(下面程序中为 res)
long long sum(int i){
long long ret=0;
while(i){
ret+=i%10;
i/=10;
}
return ret;
}
long long f(int l,int r){
long long ret=0;
for (int i=l;i<=r;i++){
ret+=sum(i);
}
return ret;
}
long long res[110]={0,315000001,325000001,335000001,345000001,355000001,365000001,375000001,385000001,
395000001,404999992,325000001,335000001,345000001,355000001,365000001,375000001,
385000001,395000001,405000001,414999992,335000001,345000001,355000001,365000001,
375000001,385000001,395000001,405000001,415000001,424999992,345000001,355000001,
365000001,375000001,385000001,395000001,405000001,415000001,425000001,434999992,
355000001,365000001,375000001,385000001,395000001,405000001,415000001,425000001,
435000001,444999992,365000001,375000001,385000001,395000001,405000001,415000001,
425000001,435000001,445000001,454999992,375000001,385000001,395000001,405000001,
415000001,425000001,435000001,445000001,455000001,464999992,385000001,395000001,
405000001,415000001,425000001,435000001,445000001,455000001,465000001,474999992,
395000001,405000001,415000001,425000001,435000001,445000001,455000001,465000001,
475000001,484999992,405000001,415000001,425000001,435000001,445000001,455000001,
465000001,475000001,485000001,494999983};//一个100大小的表
int main(){
int n,i;
cin>>n;
long long s=0;//存放和
for(i=1;i*10000000<=n;i++)//按一千万算整块
s+=res[i];
s+=f((i-1)*10000000+1,n);//然后暴力算一千万以下的小块
printf("%lld",s);
return 0;
}
Part.4 骗分的关键——猜想
4.1 听天由命
如果你觉得你的人品很好,可以试试这一招——输出随机数。
先看一下代码:
#include<stdlib.h>
#include<time.h>
//以上两个头文件必须加
srand(time(NULL));
//输出随机数前执行此语句
printf(“%d”,rand()%X);
//输出一个0~X-1的随机整数。
这种方法适用于输出一个整数(或判断是否)的题目中,答案的范围越小越好。让老天决定你的得分吧。
4.2 猜测答案
有些时候,问题的答案可能很有特点:对于大多数情况,答案是一样的。这时,骗分就该出手了。你需要做的,就是发掘出这个答案,然后直接输出。
有时,你需要运用 Part.2中学到的知识,先写出朴素算法,然后造一些数据,可能就会发现规律。
例题:炸毁计划
问题描述
敌人侵占了通往招远的黄金要道。为了保护渤海通道的安全,使得黄金能够顺利地运送到敌后战略总指挥地延安,从而购买战需武器,所以我们要通过你的程序确定这条战略走廊是否安全。
已知我们有 NN 座小岛,只有使得每一个小岛都能与其他任意一个小岛联通才能保证走廊的安全。每个小岛之间只能通过若干双向联通的桥保持联系,已知有 MM 座桥 (A_iAi,B_iBi) 表示第i座桥连接了A_iAi 与 B_iBi 这两座城市。
现在,敌人的炸药只能炸毁其中一座桥,请问在仅仅炸毁这一座桥的情况下,能否保证所有岛屿安全,都能联通起来。
现在给出 QQ 个询问 C_iCi,其中 C_iCi 表示桥梁编号,桥梁的编号按照输入顺序编号。每个询问表示在仅仅炸毁第 C_iCi 座桥的情况下能否保证所有岛屿安全。如果可以,在输出文件当中,对应输入顺序输出 yes,否则输出no(输出为半角英文单词,区分大小写,默认为小写,不含任何小写符号,每行输出一个空格,忽略文末空格)。
输入格式
第一行 三个整数 NN,MM,QQ,分别表示岛屿的个数,桥梁的个数和询问的个数。
第二行到第 M+1M+1 行 每行两个整数。第 i+1i+1 行有两个整数 A_iAi B_iBi 表示这个桥梁的属性。
第 M+2M+2 行 有 QQ 个整数 C_iCi 表示查询。
输出格式
QQ 行,表示查询结果。
样例
输入#1
2 1 1
1 2
1
输出#1
no
你发现问题了吗?那么多座桥,炸一座就破坏岛屿的联系,可能性微乎其微(除非特别设计数据)。
那么,我们的骗分策略就出来了:对于所有询问,输出 yes.果然,此算法效果不错,得 8080 分。
Part.5 特殊部分分
很多题目都有部分分,我们可以抓住其中的特点来将一个程序分成若干个程序以应对特殊测试组。
Part.6 组合
上面讲的都是单个的骗分方法,我们也可以将他们结合起来(见下面的程序)。
if(是样例) cout<<样例;
else if(是无解) cout<<-1;
else if(是小数据) 模拟/暴力/DFS
else if(是特殊数据) 特殊处理
else 听天由命
注:上面的程序是各种情况的综合,请以真实题目为准。
Part.7 结语
骗分的作用是为拿到更高的分数,但我们不能一直依靠于骗分,要学会写正解。
最后祝大家 RP++!



2747

被折叠的 条评论
为什么被折叠?



