奇技淫巧

0.如果不是秒切的话,解决问题的一般思路:

1.退而求其次:如果数据小、没有某个限制怎么办?(可以通过暴力分得到提示)

2.找到矛盾问题的根源,就是算法升级的关键,如果有了这个限制,怎么处理?

3.集中处理这个矛盾

 

 

本篇博文记录一些机智操作,简洁方法,奇技淫巧。

1.快读:

void read(int &x)
{
    char ch;bool flag=false;
    while(!isdigit(ch=getchar()))
        (ch=='-')&&(flag=true); 
    for(x=num;isdigit(ch=getchar());x=x*10+num);
     (flag)&&(x=-x);
}

注意:&x和&&前后两个的顺序

支持:整形读入。包括负数

 

2.取模优化:

inline int mod(int x)
{
    return x-p*(x/p);
}

 

 

3.线段树define操作

struct node{
    int l,r;
    int add,ch;
    int mx,hx;
    int had,hch;//一段时间内最值  
    #define ad(x) t[x].add
    #define ch(x) t[x].ch
    #define mx(x) t[x].mx
    #define hx(x) t[x].hx
    #define ha(x) t[x].had
    #define hc(x) t[x].hch
    #define l(x) t[x].l
    #define r(x) t[x].r
}t[4*N];

摘自:cpu监控

 

4.define int long long

适用于无脑调试

但是空间极其紧张,除非没时间改,否则不要用这个。

 

5.num[++num[0]]=x;

用num[0]计数,有多个数组的时候,不会弄混cnt名称

 

6.fix

当数组中可能遇见负数的时候,可以考虑采用修正值fix,将取值的区域平移。

如:NOIP2016 D1T2 天天爱跑步, 处理d[si]-2*d[lca]<0 , w[x]-d[x]<0时,取值范围在:[-n,n]

直接再加上n,平移到[0,n]就可以直接处理了。

 

7.区间中位数

二分一个中位数的值ans,大于ans的赋为1,小于-1(等于再说)

找区间内有无连续子段和大于0,判断ans+还是ans-

 

8.字符读入避免空格

ztb法:char s[] scanf("%s",s+1) 利用字符串自动剔除前面空格,并且读到下一个空格位置。

chen_zhe法:char c scanf("%s",&c) 敢情这也可以。。。。

9.Hungary匈牙利算法每次memset的小优化(默认左部点出发dfs)

①一般情况下,我们是每次找到一个右部点,vis[y]=1,之后每次memset一下vis

②但是,有的时候,右部点很多,memset一下,nleft*nright 就挂了。

而如果左部点很少,可以尝试标记左部点的vis,

因为,我们之所以标记vis,是因为,不能在dfs(pre[y])时,从pre[y]里不断返回pre[y]自己(考虑代码模拟一下)。

如果标记左部点,那么,回到自己时,会因为已经vis,直接返回0

换句话说,①方案是不让你找到y,而②方案,是回到自己再打断。本质一样。

③我们也可以用一个栈sta[],收集右部点vis的点,用弹栈并归零处理vis。

为什么复杂度在②条件下没问题??

其实,最坏和②复杂度是一样的,一般情况下更优。

因为,我们每找到一个!vis[y],就要做出选择,要么!pre[y] 返回true,要么dfs(pre[y])

返回就返回了,打断搜索。如果dfs下去,那么必然要到了一个新的左部点。

所以,每vis一个,最多多访问一个左部点。所以,sta[]中元素的数量和左部点数量是一致的。

所以,最坏情况下,和②是一样的。

 

(2018.9.8  update:其实根本可以不用每次尝试清空什么vis!!!

我们可以采用时间戳的东西,vis[i]变成int数组,表示最后一次访问这个右部点的左部点的编号!

如果这一次的没有访问i,即vis[i]!=id  那么就令vis[i]=id,就不用每次清空了!

  

int vis[N],pre[N];
int id;
bool dfs(int x){
    for(int i=hd[x];i;i=e[i].nxt){
        int y=e[i].to;
        if(vis[y]!=id){
            vis[y]=id;
            if(!pre[y]||dfs(pre[y])){
                pre[y]=x;
                return true;
            }
        }
    }
    return false;
} 
for(int i=1;i<=mx;i++){
    id++;
    if(dfs(i)) ans++;
}

 

 

10.多个点的lca

dfs序最小点和dfs序最大点取lca即可。

因为dfs总是先搜完一个子树,又因为两个点遍历时间相差最远。画图理解一下。

类似:JZ模拟赛 8.9 B组T3

 

11.区间维护最小值,支持删除,插入

1.multiset

2.差的最小值呢?两个set,第二个set维护数值set相邻两个的差值。插入删除用前驱后继处理。

详见:JZ模拟赛 8.9 A组T3

 

12.双哈希

有的时候,出题人要卡你。一个哈希值可能出现的差错是:

本来不同的两个串,通过取模运算,就可能相同了。

所以,我们通过两个串,如果两个有一个哈希值不同,就认为串不同了。

否则,认为串相同。

注意,不可能出现相同的串哈希值不同的情况,因为算法一样,得到的哈希值一定相同。

 

13.曼哈顿距离转切比雪夫距离

 (x,y) -> (x-y,x+y)重新设置点的坐标。

(x1,y1),(x2,y2) -> (x1+y1,x1-y1), (x2+y2,x2-y2)

分类讨论可以证明:|x1-x2|+|y1-y2|=max(|x1-x2|,|y1-y2|)

这样,就可以把一个加法变成最值问题。

可以处理曼哈顿距离最小生成树。

用两个set一个维护没有加入当前点集中的点,横坐标,纵坐标。

用四个数维护当前点集最小x,最大x,最小y,最大y

每次从set找四个点和最小x,最大x,最小y,最大y找最小距离加进去。

因为,只要从x找一个max,y找一个max,再取max就可以。

 

14.变进制数存储状态,状压dp

变进制法不错的讲解:NKOJ1633 神仙开山【变进制数状压DP】

还有一个应用:JZ模拟赛 8.18   A组T3

就是,根据每个数的出现次数不同,每个数位置的进制都不同。

这样,可以达到压缩状态数到最小的结果。

 

15.一点反演技巧

(i,j)=1 时,等价于:∑d d|i&&d|j u(d) =1

(i,j)!=1时,等价于:∑d d|i&&d|j u(d) =0

即通过判断i,j所有公约数的u的和是否为1,判断i,j是否互质。

证明:

i,j互质的时候,公约数只有1,u(1)=1

i,j不互质的时候,公约数就是i,j最大公约数的约数。

设gcd(i,j)=p1^q1*p2^q2*...pk^qk

那么,如果取两个以上的pi,u就是0,不影响答案。

所以,就是取p1、。。。pk一个或不取的方案数要考虑

取零个:C(k,0)个1

取一个 :C(k,1)个-1

取两个 :C(k,2)个1

。。。

所以,最后的答案是:C(k,0)-C(k,1)+C(k,2)-C(k,3)+...+C(k,k)

根据组合数的性质,不论k是奇数还是偶数,答案都是0

证毕。

 

16数学期望dp小结

 

总结:

数学期望以难以证明的性质,花样繁出的特点闻名于OI界。

其难以下手的恐惧,令不少蒟蒻心有余而力不足。

还是抓住关键的线性递推式,和期望定义 。 慢慢仔细分析。

对于期望状态的设计:

1.多个终点一个起点,就f[i]表示,从i到终点的期望步数,f[s]即为答案

2.多个起点一个终点,就f[i]表示,从起点到i的期望步数,f[t]即为答案

3.与终点无关的树形期望dp,通常往子树对i,父亲对i影响考虑,和一般的树形dp类似。

 

17.曼哈顿距离和切比雪夫距离转化

 奇技淫巧_第1张图片

 

18.gets()读入整行字符串,然后再 处理。

或者getchar()读入空格换行,scanf("%s")读入单词等

 

19.扩展欧拉定理:

$b^c\space mod p=b^{c\space mod p+\phi(p)}mod p (c>\phi(p))$

p,b可以不互质

 

20.计算三角形面积。

坐标:(x1,y1),(x2,y2),(x3,y3)

把(x3,y3)移动到原点。得到新的(x1,y1),(x2,y2)

S=|x1y2-x2y1|/2

可以避免精度误差

 

21.二分图带权最大匹配

如果左部点只连接两个边,可以认为是两个右部点之间连接了一条边。

形成了一个无向图(可能不连通),相当于给边定向。

 

 22.无理数的高精度处理

(适用于($(a+b\times\sqrt{x})^k$):

1.保留“a+b*根号”的形式:构造有序数对:(a,b),即可定义乘法。

2.精度问题:令

无理项消除,N是整数。如果$(a-b\times\sqrt{x})^k)$小于1的话,k比较大,就逼近0,k比较小,double可以承受。

例题:圆——高精无理数的处理

 

23.快速质因数分解:

如果要多次询问在1e7范围内的质因数分解,可以利用线性筛预处理每个数的最小质因子,然后不断除以mindiv,即可在低于logn时间下完成分解。

而且质因子已经自动排好序了。

例题:CF757E Bash Plays with Functions

 

24.十进制快速幂(专治高精快速幂)

和二进制快速幂原理一样。

y要循环$log_{10} y$次。

每次x要左移一位,相当于一个快速幂$log_2 {10} $次。

所以,总的复杂度其实也是:$log_{10} y \times log_2 {10} = log_{2} y$

但是,当y是一个高精度的时候,如果用十进制储存,每次/2是O(长度)的,会TLE。

然而十进制快速幂直接舍弃最后一位,可以O(1)进行移位。

(由于复杂度在低精度下是一样的,所以除了高精快速幂,并没有什么卵用)

 

 

25.爆栈的处理方法

 

26.HASH判断循环节

对于O(1)判断si是sj的循环节:

hash(sj)*p^leni+hash(si)=hash(si)*p^lenj+hash(sj)

证明方法类似kmp的循环节证明。

 

27.C(n,m)奇偶性

结论:C(n,m)为奇数,当且仅当n&m=m,即m是n的子集。

证明:

根据LUCAS定理,C(n,m)=1 mod 2 意味着,n,m二进制上的每一位,都有n是1,m是0,或者n是1,m是1.或者n是0,m是0.

如果n是0,m是1的话,那么C(0,1)=0,那么就是0了。

 

 

28.所有取值的前k最值问题

考虑每个可能的决策点是否有贡献。

1.起初把每个点的最优解找出来,带着点的id,放进堆里面。

2.从堆里取出最优解。

3.把和这个id有关的次优解找出来,放进堆里。

重复2、3

适用于一些有固定可能存在的决策点。并且能够快速找到和这个点有关的第k优的值。

例题:BZOJ3689: 异或之

[NOI2010]超级钢琴

 

29.转化研究对象:

枚举每个决策点,计算决策点包含的物品的贡献——>枚举物品,计算对能产生影响的决策点的贡献。

后者,用于计数,就是分开考虑每个元素。对于最值决策,计算之后直接对决策点取最值。

 

30.-Wall -W

工具——编译选项——编辑器——编辑时加入以下命令:-W -Wall

小错可以警告

#pragma GCC ("-W1,--stack=128000000)手动开栈。

 

 

 

 31.对于一些序列两次取值的问题(两次取值位置不相交)

可以正着dp,f[i],前i个位置选择一个

再反着dp,g[i]i~n位置选择一个。

然后枚举分界点,即可。

ans=max(ans,f[i]+g[i])

 

32.雪球数:

 

一个数的所有非空前缀组成的数都是素数。

例如:313 :3,31,313都是素数。

然而,雪球数有最大的数并不是无限的:73939133

 

 

33.NOI Linux编译

1.进入Linux虚拟机

2.桌面新建文件。打开。

3.复制代码进去。

4.重命名为my.cpp(后缀为cpp)

5.Ctrl+Alt+T 进入终端

6.输入cd Desktop进入桌面界面(忘了文件名,输入ls Desktop 显示桌面的所有文件)

7.输入g++ my.cpp -o my.exe

(有需要的话,后面加上-std=c++11 -Wall -O2)

8.如果回车之后,没有其他信息(没有error),那么编译成功。

9.接下来输入./my.exe (注意之间没有空格)表示进入这个exe

然后类似windows下的cmd,直接输入数据即可。

 (按动↑可以寻找上面进行过的命令)

 

34.半平面交atan2

排序时,求向量旋转角的时候,用atan2(y,x)比较好

既可以保证精度,而且范围是在(-Pi,Pi]的。可以表示所有的旋转角。而且,如果atan2(t,0)的话(t>0),会返回Pi/2,

并不会因为tan(Pi/2)不存在而RE等。

35.int a=floor((double)b+0.5)

适用于:FFT等需要四舍五入以及处理-0.0的情况。
(注意,负数的时候,强转int是向中取整,floor是向下取整,有所不同。看情况决定)

而且,FFT最后输出的时候,

 

for(reg i=0;i<=m;++i){
        b[i].x/=n;
        int pp=b[i].x+0.5;
        printf("%d ",pp);
    }

 

代替

for(reg i=0;i<=m;++i){
        b[i].x/=n;
        printf("%.0lf ",fabs(b[i].x));
    }

可以快一倍!

 

 还有一个东西叫round(x)

(int)y=round((double)x)

四舍五入函数。

 

 

36.FFT三次变两次:

就是把P(x)=F(x)+G(x)i

P(x)^2虚数部分/2即可。节省一次DFT

傅里叶变换(FFT)学习笔记

奇技淫巧_第2张图片

 

37.O(1)求LCA

用欧拉遍历序遍历树,然后RMQ记录深度。LCA就是之间深度最浅的,O(1)查询

详见:LCA 最近公共祖先

 

38.堆优化dij的一个小剪枝

1.不必每次把所有的出边更新到的都加入到堆里

类似spfa,如果dis[y]>dis[x]+e[i].val那么dis[y]=dis[x]+e[i].val,q.push(po(y,dis[y]))

这样常数会小一些。多次dij,优化就明显了。

2.还可以用num记录已经确定的点的个数,如果num=0直接return

适当减少了常数(但是如果要多次dij的话,别忘了清空堆)

 

39.关于平均值的二分

上面有中位数的二分,平均值也可以二分

每个数减去平均值,如果区间和大于等于0,那么可以

fzyzojP1635 -- 平均值

 

40.一个前缀问题

一个有向图,S到T经过至少k条边的最短路,这个要T自己和自己连个自环,表示保留下来

2.11考试

 

41.关心应该关心的

缩减冗余状态:各种DP中用到

合并类似转移及状态:数组平移,维护分段函数,20190211 模拟训练 A. 大猫咪

修改之间忽视并不会变的:虚树、动态DP

 

42.异或下线性方程组的自由元个数:

先变成n*(n+1)的矩阵

然后高斯消元,如果某一个id找不到,那么一定是自由元了,计数器++

注意,每次找i和消除必须在全局位置,并且用一个vis标记表示是否还能动(因为有时候存在自由元X_i,但是第i行其实还能用)

最后削成的上三角矩阵,除了无解情况,剩下的一定有唯一解

(一般的线性方程组也是这样求,只不过xor下的自由元用的比较多)

 

代码见:bzoj4671: 异或图——斯特林反演

 

 

 

43.一个通用技巧是:

一个通用技巧是:

找到两个数组f,g

f范围宽松好统计,g范围严格难统计但是和答案有直接关系,

这样,只要得到f和g的关系,就可以找到答案!

经常是可以得到f由g的表达式,然后斯特林反演或者二项式反演得到g的求法

也可以用多项式科技

数论函数的反演也可以这么做。

luoguP4841 城市规划

bzoj4671: 异或图——斯特林反演

已经没有什么好害怕的了——二项式反演+经典套路

 

44.枚举LCP

一些对于字典序大小,或者二进制下有大小比较限制的按位操作的题

枚举LCP可以有效体现比较大小的“实质”

一般外层枚举LCP,内层用DP处理方案。

来自多校的一个题——数位DP+卡位

 

45.O(n^2)插值

[学习笔记]拉格朗日插值

 

46.一种生成函数还原序列的方法

有时候,两个生成函数卷积起来,要展开以后求x^k的系数,但是k很大。

①如果一个生成函数的次方的话,就是组合意义小球放盒子了,O(1)有公式,LUCAS求组合数

②否则考虑能不能把其中一个的生成函数变成有限项,使得项数在O(n)以内

例如:自古枪兵幸运E

 

47.给DAG转移重新定向

可以做到O(sqrt(m))的出边个数。支持暴力枚举Day5

 

48.树剖维护子树&&连通块信息

PK LCT

基于动态DP的思想,记录往轻儿子的贡献。重儿子现场计算。Day5

便于打标记。便于写。(毕竟是静态树)

 

49.取Ln

1.有标号图计数,F表示所有情况,G表示连通情况。则:G=Ln(F)

反之,F=exp(G)

证明可以参考:

luoguP4841 城市规划

COGS 2353 2355 2356 2358 有标号的DAG计数

2.把乘法变成加法,

①多项式降次

付公主的背包

luoguP4705 玩游戏

②把答案大的变小,仍然满足单调性,所以取最小值方案还是不变的。

[BJOI2019]奥术神杖 取Ln直接就变成0/1分数规划了

3.碰见Ln(f)很麻烦?

求导就把f搞出来了。

4.求积分时候,处理分式分母

待定系数法。1/(ax+b)->Ln(|ax+b|)直接加上绝对值,求导回去还是成立的

 

 

50.分母没有逆元

前提是求一个分子分母都是连乘积的分式

可以考虑表示成:x*p^y的pair形式,最后上下把p的次数相减(类似扩展Lucas)

具体操作:(a,b)*(c,d)=(a*c,b+d)

然后检查(a,b):如果a%mod==0,(a,b)->(a/mod,b+1),否则(a,b)->(a%mod,b)

显然这样取模,mod的次数不会减少。

 

51.DINIC从T开始BFS常数更小

显然DINIC的瓶颈在于增广。

所以一些必然不会到达T的点就不用访问了。

所以从T开始搜分层图。

虽然一些点S不能到达,但是浪费的只是BFS的常数,反之浪费的就是增广时候的常数了。

 

 

52.

奇技淫巧_第3张图片

 

53.真·奇技淫巧

O(p)预处理,O(1)任意底数,任意质数的快速幂

$a^b=G^{log_G{a^b}}=G^{b\times log_Ga}$

然后$log_Ga$对于所有的$a \in [0,p-1]$可以预处理

对于$G^{x}$可以直接预处理

 

 54.树上点集到点集的最大值

边权为正数

一个常用的套路:一个树上点集的直径,可以直接合并两个集合得到。四个端点C(4,2)计算即可。

猫的某个随机连通块直径题、安师大的某个O(n^4)预处理二分+前缀和查询、51nod1766

 

55.编号区间点集,信息可以合并?

一个常用套路:编号区间?线段树!线段树区间维护这个区间的信息即可

Max Mex 51nod1766

 

56.不能立刻决定球的颜色?

每个球的颜色个数固定,不能放一个球就染色

可以先认为是白色,然后某一个时刻钦定k个白色成为某一个颜色。只要知道之前有多少个白色就可以了。

UOJ#449. 【集训队作业2018】喂鸽子

 

57.IDFT的trick

如果遇到矩阵乘法套多项式,最后就求一个多项式的系数的话,可以考虑用IDFT的trick降低常数

具体是:直接把转移矩阵的x换成$w_N^i$,得到$Ans(w_N^i)$最后再插值。

一个乘法的常数,比卷积的常数小太多了。

UOJ#424. 【集训队作业2018】count

 

58.__builtin

奇技淫巧_第4张图片

 

59.树上后缀排序

有的时候很有用。比EXSAM好写好调。

 

60.Cnt(S,T)

表示起点在S,终点在T集合的边的条数

不能预处理,O(n)也有点慢。

可以bitset!处理S集合的出边包括哪些编号,T集合的入边包括哪些编号,求&

 

61.bitset与字符串匹配?

[国家集训队]JZPSTR

字符集很小的时候,可以对每个字符集维护出现位置。

各种插入删除可以手写bitset进行。

xi=x%Mi+1Mi

2.

你可能感兴趣的:(奇技淫巧)