突然想到在此留下模板貌似是个不错的选择也 0.0 长期更新
虽说程序需求千变万化,但是一些短小精湛的函数块还是很值得整理收藏的。
在此提醒自己:找到一种慢的解法可能是找到最终解法的一步。
1 | Composed by dna049 at 2016-4-10 in FDU |
通用代码块
1 | //#pragma comment(linker,"/STACK:10240000,10240000") // C++ only |
数论篇
Greatest Common divisor
1 | LL gcd(LL a,LL b){ return b?gcd(b,a%b):a; } |
当然用直接 GNU 内建函数 __gcd
模乘法逆元
1 | LL inv(LL a,LL p){ // 0<a<p and gcd(a,p)=1 if(a==1) return 1; return (p-p/a)*inv(p%a,p)%p; } |
快速模加法乘法
1 | LL pow(LL x,int n){ |
对于 pow_sum 可以化为矩阵幂解决,也可以用等比数列公式,取大的模解决。
整数开根号
1 | int sqrt2(LL x){ |
自然数方幂和 $O(k)$
1 | const int N = 1e6+6; |
自然数方幂和精确版
1 | #include <boost/multiprecision/cpp_int.hpp> |
离散对数
1 | LL baby_step_giant_step(LL a,LL b,LL p){ // a^x = b mod p |
模素数开根号
1 | LL modsqrt(LL a,LL p){ // find x s.t x*x=a mod p; |
模素数幂开方
1 | LL mod_sqrt_p(LL a,LL n,LL p,int k){//find x s.t x*x=a mod p^k where (a,p)=1,p>2 |
对于模一般的 $n$,先素因子分解分别求出答案,然后用中国剩余定理求最终解。
组合数
1 | LL C(int n,int m){ |
有理数转化成小数
1 | string rational(int n,int m){ |
详细分析见我的博文。
N以内素数线性筛 $O(n)$
1 | const int N = 1e7+2; |
正整数拆分
1 | const int N = 1e5+2; |
大素数Miller-Rabin概率判别法
1 | bool Witness(LL a,LL n,LL m,int t){ LL x=pow_mod(a,m,n); if(x==1||x==n-1) return false; while(t--){ x=mul_mod(x,x,n); if(x==n-1) return false; } return true; } bool Rabin(LL n){ if(n<2) return false; if(n==2) return true; if(!(n&1)) return false; LL m=n-1; int t=0,cnt=33; while(!(m&1)){ ++t;m>>=1; } while(cnt--){ LL a=rand()%(n-1)+1; if(Witness(a,n,m,t)) return false; } return true; } |
最小素因子预处理
1 | const int N = 1e6+2; |
大整数的最小素因子
1 | LL Pollard_rho(LL n){ |
Mobius function
1 | const int N = 1e7+2; |
Euler totient function
1 | const int N = 1e7+2; |
$\pi(x)$ 函数
1 | const int N = 1e7+2; |
另一个版本:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23const int N = 1e7+2;
LL L[N],R[N];
LL getans(LL n){ // n*n < 1e14
LL rn = sqrt2(n);
for(LL i=1;i<=rn;++i) R[i]=n/i-1;
LL ln = n/(rn+1)+1;
for(LL i=1;i<=ln;++i) L[i]=i-1;
for(LL p=2;p<=rn;++p){
if(L[p]==L[p-1]) continue;
for(LL i=1,tn=min(n/(p*p),rn);i<=tn;++i){
R[i] -= (i*p<=rn?R[i*p]:L[n/(i*p)])-L[p-1];
}
for(LL i=ln;i>=p*p;--i){
L[i] -= L[i/p]-L[p-1];
}
}
LL ans = L[sqrt3(n)];
for(LL p=2;p<=rn;++p){
if(L[p] == L[p-1]) continue;
ans += R[p]-L[p];
}
return ans;
}
求奇素数的一个原根
首先,模 $m$ 有原根的充要条件是:$m=2,4,p^a,2p^a$,其中$p$为奇素数。
对于求模$p$的原根方法:对 $p-1$ 素因子分解:$p-1 = p_1^{a_1} \cdots p_s^{a_s}$ 若恒有
$$ g^{\frac{p-1}{p_i}} \neq 1(mod \; p) $$
则 $g$ 是 模$p$的原根。对于 $p^a$ 可以用 $p$ 的原根简单构造,而 $2p^a$ 的原根为 $p^a$ 的原根 与 $p^a$ 的原根和 $p^a$的和中奇数者。(证明见P150《数论基础》潘承洞)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25LL fact[N];
int factor(LL n){
int cnt = 0;
for(int i=0;LL(p[i])*p[i]<=n; ++i){
if(n%p[i]==0){
fact[cnt++] = p[i];
while(n%p[i]==0) n /= p[i];
}
}
if(n > 1) fact[cnt++] = n;
return cnt;
}
LL proot(LL p){ // p must be odd prime
int cnt = factor(p-1);
for(LL i = 1; i<p;++i){
bool flag = true;
for(int j=0;j<cnt;++j){
if(pow_mod(i,(p-1)/fact[j],p)==1){
flag = false; break;
}
}
if(flag) return i;
}
return -1;
}
求所有原根见我之前的博文
数论函数的Dirichlet乘积
1 | class Dirichlet{ |
线性同余方程
1 | int modeq(LL a,LL b,LL n){ // a*x=b mod n return x LL x,y,d; d=exgcd(a,n,x,y); if(b%d!=0) return -1; a/=d;n/=d;b/=d; return ((x*b)%n+n)%n; } |
中国剩余定理
1 | LL chinaRemain(int n,LL a[],LL m[]){//x=a[i] mod m[i] LL x,y,a1,a2,m1,m2,d; a1=a[0],m1=m[0]; for(int i=1;i<n;++i){ a2=a[i],m2=m[i]; d=exgcd(m1,m2,x,y); if((a2-a1)%d!=0) return -1; a1+=(((a2-a1)/d*x)%m2+m2)%m2*m1; m1=m1/d*m2; a1=(a1%m1+m1)%m1; } return a1; } |
FFT
1 | void change(double *x,double *y,int len,int loglen){ |
利用FFT的多项式乘法
1 | void mul(int *a,int *b,int len,int loglen,bool same){ |
快速数论变换 NFT
1 | void change(LL *x,int len,int loglen){ |
多项式类
1 | class Node{ |
大数加乘类
1 | class BigInt{ // without nft and we won't set B=10000 in nft |
$\mathbb{Z}_p$ 二次拓域
1 | LL p,q; |
矩阵类(版本1)
1 | LL mod; |
矩阵类(版本2)
1 | LL mod; |
数据结构
并查集
1 | int find(int x){ |
树状数组
1 | struct TreeArray{ |
若设原始数组为 $a$,设数字 $i$ 的二进制表示为 $d_1 \cdots d_n0\dots 0,d_n = 1$ 本质上树状数组 $s[i]$ 保存的其实是前 $n-1$ 位和 $i$ 一致,且不超过 $i$ 的位置元素之和。
线段树
1 | #define lrt rt<<1 |
RMQ 求区间最大值
1 | class RMQ{ static const int N=100005; public: int n; int a[N][20]; //a[i][j]=max(s[i]---s[i+2^j-1]) RMQ(int* s,int _n):n(_n){ for(int i=0;i!=n;++i) a[i][0]=s[i]; int len=(int)(log(double(n))/log(2.0))+2; for(int i=1;i!=len;++i){ for(int j=0;j<=n-(1<<i);++j){ a[j][i]=max(a[j][i-1],a[j+(1<<(i-1))][i-1]); } } } int Max(int l,int r){ // 0 <= l <= r < n int len=(int)(log(double(r-l+1))/log(2.0)); return max(a[l][len],a[r-(1<<len)+1][len]); } }; |
最长(严格)递增子序列
1 | int LIS(int a[],int n){ // longest increasing subsquence |
我们经常用二分答案的思想,但是其实二分答案是仅仅知道其单调的情况下的策略,实际上,对于具体的问题,我们完全可以对 $m$ 的值进行不同的处理,而非单纯的 $m=(l+r)>>1$。
最大子序列和
1 | int MSCS(int a[],int n){ // maximal sum of continue subsquence,mind overflow |
最多区间交
做法:左端点标记 1, 右端点标记 -1,然后排序,前缀和最大值即为所求。注意在端点相同时,左端点要在前。
背包
1 | const int MAX=1e5+5; |
字符串匹配的Sunday算法
1 | #include<cstring> |
字符串匹配的KMP算法
1 | void getNext(char *s,int *next){ |
整数字典树 Trie
1 | struct Trie{ |
数组型字典树
1 | const int N = 1e4+4; |
字母字典树 Trie
1 | struct Trie{ |
单调队列和单调栈
单调队列,单调栈顾名思义就是内部元素是单调的,区别就是队列是先进先出,栈是后进先出。
- 单调队列的典型应用就是 $O(n)$复杂度求给定宽度的区间在各处的最大值。
- 单调栈的典型应用是 $O(n)$ 复杂度求出以每一点为最小值的最大区间。
1 | #include <bits/stdc++.h> |
详见我的博文。
优先队列
可以使用C++ STL 的 priority_queue,查找可用 lower_bound 和 upper_bound。C++ STL 中优先队列是用堆来实现的。用途十分广泛,例如加速最小生成树,拓扑排序,等等。
堆的实现一般是用数组。我们可以用 1 作为树的根,对每一个节点 $x$ ,它的两个节点分别就是 $2x$ 和 $2x+1$ 平时都用 $x<<1,x<<1|1$ 表示。 堆只支持三个操作,
- 插入一个节点(我们实现时是插入最尾部,这样保证了是一个完全二叉树) $O(\log n)$
- 删除最大键值节点(删除根元素的值)$O(\log n)$
- 输出最大键值节点(查看根元素的值)$O(1)$
堆维持了一个性质使其能如此高效:父节点的值不小于儿子节点的值。那么显然根节点的键值最大。
那么我们在尾部插入一个节点,如果它大于其父节点,那么跟父节点互换。因为树的高度为 $O(\log n)$, 因此插入操作复杂度也就 $O(\log n)$, 删除最大节点就是先让其和最尾部的节点互换,然后删除最尾部节点,然后从头到尾的与键值大的子节点互换,直到维护了堆的性质,最后查看最大键值节点就是看根节点,显然是 $O(1)$ 的。
它牺牲了许多操作,带来了高效率。
RMQ,单调队列,单调栈,树状数组,堆,线段树,红黑树 是我掌握的也很喜欢的几个数据结构了。
拓扑排序反字典序输出
1 | const int M = 200005; |
红黑树 red-black tree
1 | class RBT{ |
图论
dfs 序
1 | ncnt = 0; // init every case |
最大流
1 | struct Node{ |
Stoer-Wagner 最小割
1 | const int N=102; |
最短路SPFA
1 | const int INF=0x3f3f3f3f; |
几何
二维凸包
1 | const int N=501; |
几类根号算法
1.$s(n) = \sum_{i=1}^{n} \lfloor \frac{n}{i} \rfloor $
1 | LL getsum(LL n){ |
2.$\sum_{i=1}^n \lfloor \frac{n}{i} \rfloor \lfloor \frac{m}{i} \rfloor f(i)$
1 | LL getsum(int n,int m){ // g[i]=f[i]+g[i-1] |
3.$h(n) = \frac{n(n-1)(n-2)}{3} - \sum_{i=2}^n h(\lfloor \frac{n}{i} \rfloor)$
1 | map<int,int> mp; |