我有独特的骗分技巧
CreeperK
2018-08-15 22:53:36
(注意:以下见解为个人观点,如有不足,请多指教。如果对卡常的几个名词$inline,register$等的原理感到疑问,请看其他dalao的博客,本篇博客不赘述。)
## -1 写在前言之前
可能这篇文章初看标题会有一些dalao认为我写的东西很低级(虽然的确挺低级)
但是,我认为也值得浪费您一点时间看一看
相信多多少少也会有一些收获
(如果没有,我深表歉意。)
# 0 前言
### 骗分法宝:打表与暴力。
暴力是NOI系列比赛中极为重要的一环。而打表就更不用说了,是上述两者的补充,虽然耍赖,却能拿分,何乐而不为?本蒟蒻今日把上述两种技巧总结一下,方便各位~~轻松AK~~。
(卡常我就不说了,我就在暴力的时候顺便用一下,优化暴力的时间复杂度就算了)
(鉴于本人经验不足,~~太懒了~~没有打过经典的骗分算法——模拟退火,就不讲了,推荐一篇给大家,更多的题目还需要自行百度:[这里](http://ruanx.pw/post/%E8%BF%91%E4%BC%BC%E7%AE%97%E6%B3%95.html))
~~说到底还不是因为我太弱才会来讲这些~~
声明:并非所有的知识都为我原创,我会尽量注明出处,如果哪位dalao认为我触犯了您的知识产权,我深表歉意,请在评论或是私信联系我,我会把出处加上。
那咱们就暂时离开恶心人的各种算法,来到骗分的天地吧。
# 1 打表
打表,顾名思义,就是__『获得一个有序表或常量表,来执行程序某一部分,优化时间复杂度』__(摘自度娘),说白了就是先暴力(题目数据较大,题目简单)或手算(题目数据较小,难度较大)出答案,然后放进代码里,直接输出。
__在我看来,打表不只有输出答案,还可以佐以我们的思考,成就一顿~~美味的~~程序。__
有些人会问:“打表还有什么技巧?不就是什么数据什么输出吗?”不急,来看道题:
$\color{Black}{-------------------------------------------}$
[洛谷P1463 POI2002,HAOI2007 反素数](https://www.luogu.org/problemnew/show/P1463)
这道题可谓是经典的打表题(个人觉得搜索简单些),但是也不能无脑直接『打出答案』,比如我一开始的打表思路就是如下的伪代码:
```cpp
int a[2e9],b[2e9],maxn=0,l;
bool sign;
for i = 1...n
计算i约数个数,存放到a数组;
if(a[i]>=maxn)maxn=a[i],l=i;
print maxn;
```
这样可以得到一整个答案表。
但暂且不谈空间大小问题,单算算输出时间复杂度就够了。
输出$2e9$个数,再假设计算机运算速度为$1e9/s$,输出速度为$2000B/s$(很难做到),那最少也要输出$1000000s$,约为11天,赛场上怎么可能呢?
所以还是要优化一些。不难看出,速度瓶颈主要在输出上,于是可以上筛表,同时反素数个数实际上很少,不用全部输出对应答案,只要输出所有反素数即可。经~~非~~精确枚举,$2e9$下的反素数共有69个。
时间太长时可能打不完,但是一般来讲这就可以了,因为暴力的时间平均也就比正解多几个$O(n)$(呸),一般来讲可以承受。
$\color{Black}{-------------------------------------------}$
### 但是,这就是打表的唯一用途了吗?再来!
## 1、获得正解
很多时候数据的种数很多,不可能全部打完,单纯的打表就不能满足我们的要求了。这个时候,__打表可以帮助我们获得正解。__
如《训练指南》P137,[LA5059(UVA1482)](https://www.luogu.org/problemnew/show/UVA1482),这道题是个典型SG函数应用,汝佳大神就是先写了个递推程序,观察了SG函数的特点,得到$n$为偶数时$SG(n)=n/2$,$n$为奇数时$SG(n)=SG(n/2)$,最后结合SG定理得出了正确的结果。
__所以说明,打表不仅仅是直接输出答案,也可以找到数据与答案的联系,甚至推导正解。__
$\color{Black}{-------------------------------------------}$
## 2、分段打表
如CSDN中的某位大神(似乎不止一位)所言:
>遇到不会的题一定要想想能不能打表,运气好都能满分。
虽然本非酋表示不信但是就是觉得好霸气好有道理$QAQ$。
遇到我们上面说反素数的问题,如果打不完怎么办?当然不能坐以待毙,肯定要奋起反击!
对于比较容易递推(可递推且依赖状态较少)的情况,我们可以请出打表的升级版:__分段打表__。
比如说,假设范围是2e9,答案种数又不如反素数那么少,那就只能让计算机算了,这时就可以以5e6为一块,打出BLOCK_SIZE(没错就是它)$\times k(k\in N)$时所对应的答案,然后零碎的部分靠暴力递推。
(可以看看[这里](https://blog.csdn.net/jiangyuze831/article/details/41551981),相信会对分段打表有一个更清晰的认识)
有人可能会说,这不是分块的思想么?事实上我觉得,分块和分段打表都是同源的,把大的任务打散成块,只是分块稍微“优雅”一些而已。
块大小的问题吗……在__可打出来的时间内尽可能把块变小__,降低分段暴力的时间复杂度,这样最后拿高分的概率更大。
分段打表还可以用来算许多计数问题(包括组合数等)的答案。
$\color{Black}{-------------------------------------------}$
## 3、借助网站/自己手算
许多计数问题可以把小数据的答案打出来,得到一个基础数列,然后可以放进[这个神奇的网站](http://oeis.org)
你没看错这清奇的画风,就是它:
![](https://i.loli.net/2018/08/18/5b782a37473c3.png)
它可以干嘛呢?
你可以试着点一下$Search$,比如说对于我们界面上的这个:(很遗憾它不是主页的页面只支持英文,所以要么靠翻译要么靠自己)
![](https://i.loli.net/2018/08/18/5b782baad28fc.png)
$\color{Gold}\colorbox{White}{黄色的框框}$是显示有多少个结果(可以搜"1,2,3,6,10,15"试试)
所有$\color{Blue}\colorbox{White}{蓝色的框框}$会显示它是如何构造出来的(即组合意义),这里说的是“$F_n$指$n$个无区别节点构造出的树的个数”。
或者,如果您知道数列的序号,也可以输入数列序号。(比如$A000001$)
下面的几行会显示原数列,加粗的是搜索的内容。
再往下拉会有几行不是很显眼的小字:
![](https://i.loli.net/2018/08/19/5b795d05edacb.png)
意思是说:
$Formula$(公式):$G.f.$($Generating$ $Function$,生成函数):$A(x)=1+T(x)-\frac{T^2(x)}{2}+\frac{T(x^2)}{2}$,$T(x)$是$A000081$($n$个节点构成有根树的个数)的生成函数。
$Example$(例子):(用独特的方式把树的样子画了出来)
上面那个正是我们需要的(可以打正解),下面那个则可以帮助我们理解。
### 多讲一句:有没有觉得$A000001$非常熟悉?做过$CF$愚人节题目的$2k$多人应该都知道,愚人节题目$CF409F$的标题就是$000001$,指的就是$OEIS$里的$A000001$,题目是要求输出该数列的第$n$项。(可见对于这些网站掌握程度的重要性)
其它是链接啦,出处啦之类的,大家有兴趣可以自己看看,我不多讲了。
有什么用?当然是可以打表了!要不然就是转化为一个较简单的组合模型,要不然就是直接或间接得到递推式或是多项式,再要不然……直接复制打表,如果不会我也帮不了您。除开这些,这也是平时了解组合数学和验证自己答案的好去处。
$\color{Grey}{-------------------------------------------}$
或者说,想要锻炼或是比赛场上,得靠自己手算,人脑找规律,就可以有如下几种方法:
1、神犇型:直接观察
2、大佬型:凭经验
3、我型:借助纸笔好好算算
然后看看,能否线性递推,或是有直接多项式(每项相互独立)可以表示,再比如有没有与组合数相关的规律,再用对应的算法验证。或者也可以看看是否是积性函数。
什么?您问我积性函数是什么?
数论中,就是__对于正整数$n$的一个算术函数$f(n)$,在$f(1)=1,gcd(a,b)=1$时满足$f(ab)=f(a)f(b)$的函数__,其中对于$a,b$不互质情况也成立的叫做__完全积性函数__。比如常见的欧拉函数,莫比乌斯函数等都是积性函数,单位函数$f(n)=n$等是完全积性函数。
想了解更多?看[这里](https://www.cnblogs.com/wfj2048/p/6537861.html)
积性函数有一个有趣而有用的性质:
### 若$n=p_1^{a_1}p_2^{a_2}p_3^{a_3}...p_k^{a_k} (a_i\in N+,p_i$为质数$)$,且$f(x)$为积性函数(不必是完全积性的),则满足:
$f(n)=f(p_1^{a_1})f(p_2^{a_2})f(p_3^{a_3})...f(p_k^{a_k})$
如果是积性函数怎么用?这就容易得多了,可以直接快速递推,~~麻麻再也不用担心我会超时啦~~。这样,可以只关注$f(p_i^{a_i})$的值,而一般这个值有很显而易见的规律,或者可以手推,可能就可以打出正解了!
$\color{Grey}{-------------------------------------------}$
## 于是有几条打表的总结技巧:
1、不要一开始就全部输出,首先先输出一些比较小的,以便找到规律,或是大致推断答案的种数,是否能直接放到数组里输出。
2、就算是打表程序,为了在规定时间内打完,也要制定策略,优化复杂度(如反素数),否则打到比赛结束没打完,就得不偿失了。
3、打表程序要力求简单而正确,其次是高效,才能发挥打表的作用。
4、数据种类较多时,主要以发现规律为主,无脑输出为辅。
5、打表有很多变种,并不是只能输出答案。
__6、不要太信任打表,有时间最好想想正解。大多数题目不能用打表做,平时要多练,直接打表输出是赛场上的方法。__
$\color{Grey}{-------------------------------------------}$
```cpp
```
# 2 暴力
暴力可谓是一个很深的学问。大多数时候,比如NOIPTG,普通选手(看到这里的各位dalao除外)能想出一两道题的正解,剩下的都得靠暴力拿部分分。
不要小瞧了这部分分,据yanQval大神所言,如果NOIP2016TG中,只用裸暴力,而且代码本身没有问题,对于下面六道题:
![](https://cdn.luogu.com.cn/upload/pic/28863.png)
可以拿$100+60+88+100+65+85=498$(此处感谢@凯瑟琳98 dalao指出我的算术错误)分!~~我就别想了qwq~~
详细信息:
$\color{Grey}{-------------------------------------------}$
$\color{Red}T1$、$\color{Blue}20/20$的数据点 模拟
$\color{Red}T2$、$\color{Blue}12/20$的数据点
$\color{Green}1-5$:$O(n^2)$暴力求出第几秒第几个人在第几点过去了
$\color{Green}6-8$:(退化成链)第X结点第Y秒钟观察,则起点只有在x-y~y之内,判断是否存在$O(n)$。
$\color{Green}9-12$:(起点在1号)所有人时间固定,某个时间是否有人过来,或终点就在子树内,递推$/BFS$
$\color{Red}T3$、$\color{Blue}22/25$的数据点 见下面讲解
$\color{Red}T4$、$\color{Blue}20/20$的数据点 二维前缀和(正解)
$\color{Red}T5$、$\color{Blue}13/20$的数据点(普通堆模拟)
$\color{Red}T6$、$\color{Blue}17/20$的数据点 状压DP(洛谷上搜索+剪枝可过)
$\color{Grey}{-------------------------------------------}$
(当然D1T1、D2T1正解相对比较容易,所以打的是正解)
当然这是最乐观估计,实际上400分应该是差不多的,但是单暴力能拿400分也很不错了啊!
$\color{Black}{---------------------------------------------}$
讲得有点多了,现在回到正题上来。
__所谓暴力,就是不用高级算法,用模拟,搜索,裸DP等等,复杂度感人,但是可以保证正确,还比较好打。__
暴力涉及的算法很多:
- 普通状压DP/无优化DP
- 枚举(子集,染色等等)
- DFS/BFS
- 迭代加深搜索IDA*
- 折半搜索
- 双向宽搜
- 一些看似不是暴力但本质是暴力的(比如下面那个Floyd)
- ……
还可以用一些碰运气的做法:比如人工定个下界,对于DP就是只枚举$i-k\le j\le i$的$f[i]=min$ { $f[j]+cost(i,j)$ },这样有点碰运气(有些时候的确可以证明是对的),但是可以优化暴力的时间复杂度。
出于可暴力的题目太多,我就不讲完了,重点挑一道题来看一看如何针对题目数据点分析暴力方法吧。
$\color{Black}{------------------------------------------}$
请见[P1850 换教室](https://www.luogu.org/problemnew/show/P1850),NOIP2016TG中暴力(理论上)拿分最高的题。(以下为yanQval大神所言,本人代码还没有调对……)
然后下面是25个数据点的范围:
![](https://i.loli.net/2018/08/16/5b74f68c3389b.png)
注意到结点个数$v\le300$,首先可以Floyd预处理任意两点间距离,然后该怎么办呢?
观察可换教室的申请数$m$,有19个数据点的$m\le2$,如此神奇的共同点肯定是有用的!
当然有用了。考虑枚举换的$m$个教室,枚举复杂度为$O(n^m)$,分类讨论是否通过是$O(2^m)$,总时间复杂度为$O(n^m\times 2^m)$。但是对于$m$在$2$以下的数据,复杂度仅为$O(n^2\times 2^2)$,可以承受。因此,我们就这样拿到了19个数据点!
哪怕常数大,应该也可以拿60多分吧!
$\color{Black}{-------------------------------------------}$
我们还可以做得更好。$m>2$时,有三个数据点#$11$,#$15$,#$19$具有特殊性质$2$。特殊性质2是什么呢?
![](https://cdn.luogu.com.cn/upload/pic/28884.png)
(洛谷图床万岁)
$k_i$就是申请通过的概率。也就是说一定能通过,然后就可以愉快地DP,当然这不属于我要讲的内容了。
$\color{Black}{-------------------------------------------}$
__这道题只是一个例子,总的来讲,只要①想出暴力算法,先解决基本数据点,②然后再找到剩下数据点的共性,从特殊性质入手,解决特殊问题,就可以尽可能多地拿分。暴力,是非常重要的方法。__
$\color{Black}{------------------------------------------}$
### 关于如何分类处理不同的数据点嘛……这可以用结构体,或者,如果您用C++,可以写在命名空间($namespace$)里,这样不同命名空间里面的变量、函数可以重名,写起来不容易错,比较方便。例如:
```cpp
namespace task1{
int a,b...;
char c...;
void solve(){
...
}
}
namespace task2{
int a,b...;
char c...;
void solve(){
...
}
}
...
int main(){
scanf("%d",&n);
if(n is situation1)task1::solve();
if(n is situation2)task2::solve();
...
}//调用和平时相同,注意是::
```
$\color{Black}{------------------------------------------}$
~~真觉得试炼场应该加一个:~~
![](https://cdn.luogu.com.cn/upload/pic/28862.png)
## 秋豆麻袋!还没完!
当然,这只是暴力的一角,如果想要拿到更高的分,在许多范围比较紧的时候,偏偏(像我这种人)常数又很大,就拿不到应该拿的分了。
$\color{Black}{------------------------------------------}$
也许就这样说还不能领悟到暴力骗分的精髓,那让我们实践一下吧!
实验名称:暴力
实验对象:[P2119 魔法阵](https://www.luogu.org/problemnew/show/P2119) NOIP2016PJT4
使用账号:我的小号@K423(U46777)
应用算法:$O(m^3)->O(n^3)$暴力(有略优化,但上界为$O(n^3)$)
一开始:
![https://www.luogu.org/record/show?rid=9392513](https://cdn.luogu.com.cn/upload/pic/29055.png)
代码主程序如下:
```cpp
//0
int main(){
scanf("%d%d",&n,&m);//1
for(int i=1;i<=m;i++){
scanf("%d",&b[i].a);
b[i].id=i; k[b[i].a]++;//2
maxn=max(maxn,b[i].a);
}
sort(b+1,b+m+1,item_cmp);//3
for(int i=1;i<=m;i++){
if(b[i].a==b[i-1].a)st[i]=st[i-1];
else st[i]=i;
}
for(int i=1;i<=m;i++){//4
for(int j=st[i]+k[b[i].a];j<=m;j++){//5
if(((b[j].a-b[i].a)&1)==1)continue;
int tmp=(b[j].a-b[i].a)/2;
if(tmp+b[j].a>=maxn)break;//6
for(int g=st[j]+k[b[j].a];g<=m;g++){//7
if(tmp+b[g].a>maxn)break;
e=st[g]+k[b[g].a];//8
while(b[e].a-b[g].a<tmp && e<=m){
e+=k[b[e].a];
}//9
if(b[e].a-b[g].a!=tmp)continue;
if(b[g].a-b[j].a<=6*tmp)continue;
s[b[i].id][0]+=k[b[e].a];
s[b[j].id][1]+=k[b[e].a];
s[b[g].id][2]+=k[b[e].a];
for(int l=st[e];l<st[e]+k[b[e].a];l++)s[b[l].id][3]++;//10
}
}
}
for(int i=1;i<=m;i++){
for(int j=0;j<4;j++)printf("%d ",s[i][j]);//11
printf("\n");
}
}
```
注意上面标出的12(0~11)处可以优化的地方。
0、赖皮$O_2$
1、可以加上快读(我的玄学程序加快读更慢了)
2.4.5.7.8、可以改一下方式,不用这种储存(见下面代码),可以更快
3、根本不用sort,因为权值较小,可以用桶排序
6、还有更紧的上界
5.9、可以用二分查找
10、因为有相同值,可以存值而不是物品。
11、快速输出和以值为基本输出
$\color{Black}{-------------------------------------------}$
中间提交:
![](https://cdn.luogu.com.cn/upload/pic/29058.png)
其中的最优解:(洛谷评测机升级之后快了很多……但还没低于4000msqwq)
![https://www.luogu.org/record/show?rid=9441811](https://cdn.luogu.com.cn/upload/pic/29059.png)
代码:
```cpp
// luogu-judger-enable-o2
void print(int x){
if(x>=10)print(x/10);
putchar(x%10+'0');
}//快速输出
int main(){
scanf("%d%d",&n,&m);
for(register int i=1;i<=m;++i){
scanf("%d",&v[i]);
maxn=maxn>v[i]?maxn:v[i];
w[v[i]]++;//桶排序
}
for(register int i=1;i<=maxn;i++){
if(w[i]){
k[++tot]=i;
sum[i]=w[i];//权值存储至k数组
}
}
for(register int i=1;i<=tot;++i){//register
for(register int j=i+1;j<=tot;++j){//register
if(((k[j]-k[i])&1)==1)continue;//等价于(k[j]-k[i])%2==1
int tmp=(k[j]-k[i])>>1;//>>1等价于/2
if(7*tmp+k[j]>=maxn)break;//7*tmp为更紧的上界,因为k[e]至少要比k[j]大(6+1)*tmp。
for(register int g=j+1;g<=tot;++g){//register
if(k[g]-k[j]<=6*tmp)continue;
if(tmp+k[g]>maxn)break;
e=lower_bound(k+g+1,k+tot+1,k[g]+tmp)-k;//二分
if(k[e]-k[g]!=tmp)continue;
t=sum[k[i]]*sum[k[j]]*sum[k[g]]*sum[k[e]];
s[k[i]][0]+=t/sum[k[i]];
s[k[j]][1]+=t/sum[k[j]];
s[k[g]][2]+=t/sum[k[g]];
s[k[e]][3]+=t/sum[k[e]];
}
}
}
for(register int i=1;i<=m;++i){
for(register int j=0;j<4;++j)print(s[v[i]][j]),putchar(' ');//快速输出&s数组存对应值的次数
printf("\n");
}
}
```
竟然一下快了8000ms!理论复杂度不变(虽然有二分,但是上界仍保持$O(n^3)$)!
原来是65分,几个优化就到了85分(不开O2就80分)!如果“中间”数据的点数更多,我们可以拿更多分!
这20分,可是有$EDJ->YDJ$,$Ag->Au$的魔力的!
再总结一下优化的地方:
1、快速输出
2、快速排序转桶排序
3、更换存储方式
4、二分
~~好像就剩算法没有改成正解了~~
(具体本题暴力的更快做法嘛……我最近看见一个3000ms以下的暴力,可惜后三个点太大,实在是过不了,大家可以看看评测记录里耗时比1000ms要长的代码,大多都是暴力,会觉得非常神奇的。)
(出于那些代码不是我本人写的,我就不拿来用了)
$\color{Black}{-------------------------------------------}$
综上可知,暴力不是直接无脑上代码的,对暴力进行一些剪枝,就可以拿到更多的分。当然,这个是靠多练题,多总结,就可以掌握暴力的方法~~,轻松AKIOI~~。
## 2-EX 随机化算法
骗分,怎能少了随机化和模拟退火!(输出随机数骗分这种就算了)(模拟退火因为我没经验,就不讲了,参见上面声明下方。)
随机化,即指『对于有些具有瑕疵的算法,配上随机数减小误差』,这可以得到意想不到的结果。
我知道有些dalao有强迫症,不准许有随机数出现在自己的程序里。这我可以理解,因为蒟蒻我平时连暴力都不敢打,更别说把自己的命运交给随机数了。但实际上,有些时候随机数异常有效,不仅代码比正解短,还跑的更快,虽然有些错误的概率,但是这也是可以接受的!
(比如您的Treap里就有随机数,复杂度很大程度上是由随机数决定的,期望是$O(logn)$而已,极小可能下还是会退化成链,但没有它,Treap就不能发挥作用了)
## 举个例子:
大家应该都知道,往地上丢火柴可以算出粗略π值,到程序实现时,可以用“投点法”代替,一个圆内接于正方形,随机若干次,可以算出π/4的粗略值。(这只是随机化的一个例子,平时还是用$acos(-1.0)$吧)
```cpp
#include<cstdlib>
#include<cstdio>
#include<ctime>
using namespace std;
const int MAXN=100000;//投掷次数
const int RAND_M=32767;//上界
int tot=0;//出现次数
int main(){
srand(time(NULL));
for(int i=1;i<=MAXN;i++){
double a=(rand()%RAND_M)*1.000/RAND_M;
double b=(rand()%RAND_M)*1.000/RAND_M;//生成随机实数
if((a-1.00/2)*(a-1.00/2)+(b-1.00/2)*(b-1.00/2)<=1.00/4)tot++;//圆方程(x-a)^2+(y-b)^2=r^2
}
printf("%lf",tot*1.000/MAXN*4);
}
```
会输出3.139960等,与π=3.1415差的不是很远。如果增大次数,还能更接近。
再比如百度百科中的,枚举一个字符串的四个字符的组合种数,判断是否满足AAAA,AABB,ABBA,ABAB中的一种,就可以用随机化代替枚举,也可以在较高的概率下得到答案。
$\color{Black}{-------------------------------------------}$
正如这两个例子,__在枚举情况过多甚至没有上界时,可以把程序交给随机化,反倒可以收获意想不到的效果。__
还有一些,是因为__正解比较麻烦或是本来算法就要随机数__,所以才加入随机数的。
## 1、最大团问题
最大团问题($Maximum$ $Clique$ $Problem$,$MCP$)意即求出所含结点数最多的完全子图(结点之间两两有连边的子图)。
这是一个经典的$NPC$问题,常用算法都是搜索,贪婪启发式算法,模拟退火等等,其实本问题用随机数做也不错。参考[这里](https://blog.csdn.net/BlackJack_/article/details/73555052)
__题目__:参考BZOJ 4080
__题意__:给出平面上$N(N\le100)$个点的坐标,选出一个点集$S$,使得$S$中的点两两之间欧几里得距离不超过$D$,问$|S|$的最大值,并输出$S$中的点编号。如有多解,任意输出一个。
这是一个最大团的模板,就用这道题来讲一下随机化贪心求最大团吧。
基本思路就是每次随机生成一个排列(就是随机交换$n$次),然后直接$O(n)$扫一遍,再$O(n)$把距离大于$D$的排掉(一般来讲这样就足够了,最大团一般$n$都不会超过100,我也不清楚是否有更低复杂度的。)遇到更大的答案更新即可。
主过程代码如下:
```cpp
T=2000;//随机次数
for(int i=1;i<=T;i++){
random_shuffle(p+1,p+n+1),a[0]=0;//algorithm自带随机打乱函数
for(int j=1;j<=n;j++){//枚举所有点
for(int k=1;k<=a[0];k++) if(dist(p[j],a[k])>d*d) break;//如果当前集中的某个点距离大于D则退出
if(k>a[0])a[++a[0]]=a[j];//如果没问题,大小++,加入这个元素。
}
if(a[0]>tot){
tot=a[0];
for(int j=1;j<=ans;j++)ans[j]=a[j];
}//更新
}
```
这样写比较简单,同时也很难有考虑不到的情况(因为随机打乱过),所以推荐采用这种写法。(其实也可以用bitset但是本题规模似乎不需要)
这样随机化,代码简短,比起回溯效率更高,比起最大独立集更易于编写,不失为一种拼人品的好算法。
## 2、字符串哈希
蛤希嘛,历来就是个靠人品的东西,往往都会用一些__大质数__作为模数。
但是用什么比较好,至今未下定论。有些时候反而合数比质数好,而且还有些毒瘤出题人专门卡常用的大质数(比如$10^9+7,998244353$,还有~~某位长者的生日~~等等)
于是呢,随机模数蛤希就登场啦,随便随机一个初始的$MOD$,只要限定一个范围,足够大就行。同理,也可以在$\sqrt n$附近选取一个值作为分块的块大小,免得有人卡。
当然,不排除有些极度毒瘤的出题人,出一大堆数据,包您~~满意~~被卡的舒舒服服。
(所以呢,如果有时间有能力(其实不差多少),还是打打双哈希比较好)
$\color{Black}{-------------------------------------------}$
还有舍伍德、拉斯维加斯算法等,各有用处,百度上都非常的详细了。我也没有什么特别好的例子(如果Treap不算的话),就建议大家多看别人的博客,自己搜索,收获更大。
随机化算法比较难以驾驭(毕竟太随机了),但是多做一些题,还是可以掌握技巧的。
# 3 总结
许多时候,考场上因为种种原因,不一定能想出正解,今天的骗分就非常重要了。
但是,打表和暴力并不是大家所想的那样,就是普通地上代码,优化可以帮助我们拿更多的分。__这些看似朴素的算法,实际上也需要多思考多优化,难度和重要性并不比别的算法低。__
希望这份总结能帮到大家,如有不足,请多指正!谢谢!
# 4 鸣谢
### @ComeIntoPower 打表与暴力的许多点子,文章上的建议
@白井黑子 卡常方面咨询
各位浴谷网校负责人 关于NOIP暴力讲解
@dyx666 @楚阳 等等同机房的dalao们(过多,不一一列出) 给予~~肉体和~~精神上的支持
https://sm.ms 提供一部分图床
http://oeis.org 提供技术支持
www.baidu.com 提供搜索引擎
我在改博客期间用过的三台电脑 给我提供基础
以及各位写了我引用的博客和知乎的dalao们,在此衷心感谢!
~~如果认为有用,请点个赞,谢谢!~~