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总是先搜完一个子树,又因为两个点遍历时间相差最远。画图理解一下。
类似: B组T3
11.区间维护最小值,支持删除,插入
1.multiset
2.差的最小值呢?两个set,第二个set维护数值set相邻两个的差值。插入删除用前驱后继处理。
详见: 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
变进制法不错的讲解:
还有一个应用:
就是,根据每个数的出现次数不同,每个数位置的进制都不同。
这样,可以达到压缩状态数到最小的结果。
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.曼哈顿距离和切比雪夫距离转化
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时间下完成分解。
而且质因子已经自动排好序了。
例题:
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优的值。
例题:
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
37.O(1)求LCA
用欧拉遍历序遍历树,然后RMQ记录深度。LCA就是之间深度最浅的,O(1)查询
详见:
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,那么可以
40.一个前缀问题
一个有向图,S到T经过至少k条边的最短路,这个要T自己和自己连个自环,表示保留下来
41.关心应该关心的
缩减冗余状态:各种DP中用到
合并类似转移及状态:数组平移,维护分段函数,
修改之间忽视并不会变的:虚树、动态DP
42.异或下线性方程组的自由元个数:
先变成n*(n+1)的矩阵
然后高斯消元,如果某一个id找不到,那么一定是自由元了,计数器++
注意,每次找i和消除必须在全局位置,并且用一个vis标记表示是否还能动(因为有时候存在自由元X_i,但是第i行其实还能用)
最后削成的上三角矩阵,除了无解情况,剩下的一定有唯一解
(一般的线性方程组也是这样求,只不过xor下的自由元用的比较多)
代码见:
43.一个通用技巧是:
一个通用技巧是:
找到两个数组f,g
f范围宽松好统计,g范围严格难统计但是和答案有直接关系,
这样,只要得到f和g的关系,就可以找到答案!
经常是可以得到f由g的表达式,然后斯特林反演或者二项式反演得到g的求法
也可以用多项式科技
数论函数的反演也可以这么做。
44.枚举LCP
一些对于字典序大小,或者二进制下有大小比较限制的按位操作的题
枚举LCP可以有效体现比较大小的“实质”
一般外层枚举LCP,内层用DP处理方案。
45.O(n^2)插值
46.一种生成函数还原序列的方法
有时候,两个生成函数卷积起来,要展开以后求x^k的系数,但是k很大。
①如果一个生成函数的次方的话,就是组合意义小球放盒子了,O(1)有公式,LUCAS求组合数
②否则考虑能不能把其中一个的生成函数变成有限项,使得项数在O(n)以内
例如:
47.给DAG转移重新定向
可以做到O(sqrt(m))的出边个数。支持暴力枚举
48.树剖维护子树&&连通块信息
PK LCT
基于动态DP的思想,记录往轻儿子的贡献。重儿子现场计算。
便于打标记。便于写。(毕竟是静态树)
49.取Ln
1.有标号图计数,F表示所有情况,G表示连通情况。则:G=Ln(F)
反之,F=exp(G)
证明可以参考:
2.把乘法变成加法,
①多项式降次
②把答案大的变小,仍然满足单调性,所以取最小值方案还是不变的。
取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.
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.编号区间点集,信息可以合并?
一个常用套路:编号区间?线段树!线段树区间维护这个区间的信息即可
51nod1766
56.不能立刻决定球的颜色?
每个球的颜色个数固定,不能放一个球就染色
可以先认为是白色,然后某一个时刻钦定k个白色成为某一个颜色。只要知道之前有多少个白色就可以了。
57.IDFT的trick
如果遇到矩阵乘法套多项式,最后就求一个多项式的系数的话,可以考虑用IDFT的trick降低常数
具体是:直接把转移矩阵的x换成$w_N^i$,得到$Ans(w_N^i)$最后再插值。
一个乘法的常数,比卷积的常数小太多了。
58.__builtin
59.树上后缀排序
有的时候很有用。比EXSAM好写好调。
60.Cnt(S,T)
表示起点在S,终点在T集合的边的条数
不能预处理,O(n)也有点慢。
可以bitset!处理S集合的出边包括哪些编号,T集合的入边包括哪些编号,求&
61.bitset与字符串匹配?
[国家集训队]JZPSTR
字符集很小的时候,可以对每个字符集维护出现位置。
各种插入删除可以手写bitset进行。
xi=x%Mi+1Mi
2.