算法竞赛板子
排序
快速排序
void qs(int a[],int l,int r){
if(l>=r)return;
int i=l-1,j=r+1,x=a[l+r>>1];
while(i<j){
do i++;while(a[i]<x);
do j--;while(a[j]>x);
if(i<j)swap(a[i],a[j]);
}
qs(a,l,j);
qs(a,j+1,r);
}
归并排序
void merge_sort(int a[],int l,int r){
if(l>=r)return;
int mid=l+r>>1;
merge_sort(a,l,mid);
merge_sort(a,mid+1,r);
int idx=l;
int i=l,j=mid+1;
while(i<=mid && j<=r){
if(a[i]>a[j])temp[idx++]=a[j++];
else temp[idx++]=a[i++];
}
while(i<=mid){
temp[idx++]=a[i++];
}
while(j<=r){
temp[idx++]=a[j++];
}
for(int k=l;k<=r;k++)
a[k]=temp[k];
}
单调性算法
二分
整数二分
int l=0,r=100;
while(l<r){
int mid=l+r+1>>1;
check(mid)?l=mid:r=mid-1;
}
cout<<l;
浮点二分
esp要比保留小数多1位,比如保留3位小数,esp选择1e-4,精度在不出现精度问题的前提下越高越好(举例)。
const double esp=1e-4;
double l=0,r=1000;
while(r-l>esp){
double mid=(l+r)/2;
check(mid)?l=mid:r=mid;
}
单调栈
for(int i=n;i>=1;i--){// 1.
while(t && r[st[t]]<=r[i])t--;// 2.
res[i]=st[t];// 3.
st[++t]=i;// 4.
}
滑动窗口
//getmax
for(int i=1;i<=n;i++){
int j=i-k+1;
if(h<=t && q[h]<j)h++;
while(h<=t && a[i]<=a[q[t]])t--;
q[++t]=i;
if(j>=1)cout<<a[q[h]]<<" ";
}
puts("");
h=0,t=-1;
//getmin
for(int i=1;i<=n;i++){
int j=i-k+1;
if(h<=t && q[h]<j)h++;
while(h<=t && a[i]>=a[q[t]])t--;
q[++t]=i;
if(j>=1)cout<<a[q[h]]<<" ";
}
数学
乘法逆元
a b % m o d = a ∗ i n v ( b ) % m o d \frac{a}{b} \% mod = a * inv(b) \% mod ba%mod=a∗inv(b)%mod
- 费马小(mod为质数)
i n v ( b ) = b m o d − 2 % m o d inv(b) = b^{mod-2}\%mod inv(b)=bmod−2%mod - 扩展欧几里得法
void exgcd(ll a, ll b, ll &x, ll &y)
{
if (b == 0)
{
x = 1, y = 0;
return;
}
exgcd(b, a % b, y, x);
y -= a / b * x;
}
ll inv(ll n)
{
ll x, y;
exgcd(n, mod, x, y);
return x;
}
- 线性逆元
vector<int> inv(n + 1);
inv[1] = 1;
for (int i = 2; i <= n; ++i)
{
inv[i] = (long long)(mod - mod / i) * inv[mod % i] % mod;
}
筛法
- 线性筛
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;
int p[N],cnt,v[N];
int main() {
int n;
cin>>n;
for(int i=2;i<=n;i++){
if(!v[i]){
p[cnt++]=i;
}
for(int j=0;p[j]*i<=n;j++){
v[p[j]*i]=p[j];
if(i%p[j]==0)break;
}
}
cout<<cnt;
};
分解质因数
时间复杂度: n \sqrt{n} n
// 首先,对于每个x%i 一定是质数,因为如果(2~i-1)的数字都被除掉了,所以满足if件i不可能是合数。
// 其次,大于sqrt(n)的因子只可能有1个,所以只需要遍历到sqrt(n)就行了,剩下一个如果x>1 那么x就是大于sqrt的质因子
void solve(){
int x;
cin>>x;
for(int i=2;i<=x/i;i++){
int cnt=0;
while(x%i==0){
x/=i;
cnt++;
}
if(cnt)cout<<i<<" "<<cnt<<endl;
}
if(x>1)cout<<x<<" "<<1<<endl;
cout<<endl;
}
欧拉函数
公式:1~N 与N互质的个数为欧拉函数
p
h
i
(
N
)
=
N
∗
p
1
−
1
p
1
.
.
.
p
m
−
1
p
m
phi(N)=N*\frac{p_1-1}{p_1}...\frac{p_m-1}{p_m}
phi(N)=N∗p1p1−1...pmpm−1
- 当 g c d ( a , b ) = 1 gcd(a,b)=1 gcd(a,b)=1时, ϕ ( a ) ∗ ϕ ( b ) = ϕ ( a b ) \phi(a)*\phi(b)=\phi(ab) ϕ(a)∗ϕ(b)=ϕ(ab)
- a ∣ b a|b a∣b时, ϕ ( a b ) = ϕ ( a ) ∗ b \phi(ab)=\phi(a)*b ϕ(ab)=ϕ(a)∗b
- p p p是质数, ϕ ( p n ) = p n − 1 ∗ ( p − 1 ) \phi(p^n)=p^{n-1}*(p-1) ϕ(pn)=pn−1∗(p−1)
- 求解
ϕ
(
n
)
\phi(n)
ϕ(n)
思路:试除法求质因子,求的过程中计算即可,时间复杂度 O ( n ) O(\sqrt{n}) O(n)
int n;
cin>>n;
ll res=n;
for(int i=2;i<=n/i;i++){
if(n%i==0)res=res/i*(i-1);
while(n%i==0)n/=i;
}
if(n>1)res=res/n*(n-1);
cout<<res<<endl;
-
求解 ∑ i = 1 n ϕ ( i ) \sum_{i=1}^n\phi(i) ∑i=1nϕ(i)
思路:可以用埃氏筛法或线性筛法,要运用到上面推导的三个性质。
- 埃氏筛法
/*
埃氏筛法的过程中,我们对质数进行翻倍,那么每个数字会被自己所有的质因子筛一遍,我们根据这个性质来“顺便”求解欧拉函数
*/
#include <iostream>
const int N=1e6+5;
int phi[N];
int main()
{
int n;
cin>>n;
long long res=1;
for(int i=1;i<=n;i++)phi[i]=i;
for(int i=2;i<=n;i++){
if(phi[i]==i){
for(int j=i;j<=n;j+=i){
phi[j]=phi[j]/i*(i-1);
}
}
res+=phi[i];
}
cout<<res;
}
- 线性筛法
/*
利用线性筛法的特点:每个数字有且仅被最小质因子筛一遍
*/
#include <iostream>
const int N=1e6+5;
typedef long long ll;
using namespace std;
int phi[N]={0,1};
int p[N],cnt;
bool book[N];
int main()
{
int n;
cin>>n;
ll res=1;
for(int i=2;i<=n;i++){
if(phi[i]==0)p[++cnt]=i,phi[i]=i-1;//没被筛到过,表明是质数 性质三
for(int j=1;p[j]<=n/i;j++){
if(i%p[j]==0){
phi[i*p[j]]=phi[i]*p[j];//性质2
break;
}
phi[i*p[j]]=phi[i]*phi[p[j]];//性质1 phi[p[j]]在之前以及被计算过
}
res+=phi[i];
}
cout<<res;
}
中国剩余定理
字符串
字符哈希
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull ;//2^64-1;
//给你一个数字s,s<1e10,有q(q<1e6)次询问,每次询问两个区间[a~b] [c~d],求这段区间的数字是否相同;
const int N=1e6+5;
const int P=131;//131 13331
// mod=2^64 %mod %mod
char s[N];
ull hs[N],p[N]={1}; //p[i]=10^i=p^i
ull getHV(int l,int r ){//得到区间[l~r]的哈希值;
return hs[r]-hs[l-1]*p[r-l+1];
}
int main(){
int q;
scanf("%s",s+1);//下标1开始;
int n=strlen(s+1);
for(int i=1;i<=n;i++){
hs[i]=hs[i-1]*P+s[i];
p[i]=p[i-1]*P;
}
scanf("%d",&q);
while(q--){
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
if(getHV(a,b)==getHV(c,d)){
cout<<"Yes\n";
}
else{
cout<<"No\n";
}
}
}
KMP
scanf("%s",s+1);
scanf("%s",p+1);
int ls=strlen(s+1),lp=strlen(p+1);
//先求出匹配串的next数组
for(int i=2,j=0;i<=lp;i++){
while(j && p[j+1]!=p[i])j=ne[j];
if(p[j+1]==p[i])j++;
ne[i]=j;
}
//再用得到的next 去匹配s
for(int i=1,j=0;i<=ls;i++){
while(j && p[j+1]!=s[i])j=ne[j];
if(p[j+1]==s[i])j++;
if(j==lp){//匹配成功
cout<<i-lp+1<<endl;
j=ne[j];
}
}
数据结构
Trie
const int N=100005;
//最坏情况下,每个字符都要再新节点,因此N取输入字符总数; 26取决字符的范围
int trie[N][26],idx,ed[N];
void insert(char * str){
int p=0;//0代表树根;
for(int i=0;str[i];i++){
int ch=str[i]-'a';
if(!trie[p][ch]){
trie[p][ch]=++idx;
}
p=trie[p][ch];
}
ed[p]++;
}
bool search(char * str){
int p=0;
for(int i=0;str[i];i++){
int ch=str[i]-'a';
if(!trie[p][ch])return false;
p=trie[p][ch];
}
return ed[p];
}
ST表
st表是一种能够很好地完成一种特定的RMQ的算法。
因为局限性比较大,不能反复修改但是时间复杂度非常优秀。
预处理
O
(
n
l
o
g
2
n
)
O(nlog_2n)
O(nlog2n) 查询
O
(
1
)
O(1)
O(1)
设
S
T
M
a
x
[
i
]
[
j
]
STMax[i][j]
STMax[i][j]:从
i
i
i开始的区间长度为
2
j
2^j
2j的最大值,即区间范围为
[
i
,
i
+
2
j
−
1
]
[i,i+2^j-1]
[i,i+2j−1]。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int STMax[N][23];
int lg[N];
int query(int l,int r ){
int k=lg[r-l+1];
return max(STMax[l][k],STMax[r-(1<<k)+1][k]);
}
int main() {
int n,m;
for(int i=2;i<N;i++){
lg[i]=lg[i/2]+1;
}
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&STMax[i][0]);
}
for(int j=1;j<=lg[n];j++){
for(int i=1;i+(1<<j)-1<=n;i++){
STMax[i][j]=max(STMax[i][j-1],STMax[i+(1<<(j-1))][j-1]);
}
}
while(m--){
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",query(l,r));
}
}
并查集
- 查询
int find(int x)
{
f[x]==x? x:f[x]=find(f[x]);
}
- 合并
void merge(int i, int j)
{
fa[find(i)] = find(j);
}
分块
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N =5e5+5;
const int M =N;
int a[N],bl[N];
int st[M],ed[M];
ll sum[M];
int bn;
void init(int n){
bn = sqrt(n);
for(int i = 1; i <= bn; i++){
st[i] = bn * (i-1) + 1;
ed[i] = bn * i;
}
ed[bn]=n;//把最后的放最后一块里
for(int i = 1; i <= bn; i++){
for(int j = st[i]; j <= ed[i]; j++){
bl[j] = i;
sum[i] += a[j];
}
}
}
ll ask(int l, int r){
ll res = 0;
int lbk = bl[l], rbk = bl[r];
if(lbk == rbk){
for(int i = l; i <= r ; i++){
res += a[i];
}
}
else{
for(int i = l; i <= ed[lbk]; i++){
res += a[i];
}
// cout<<res<<endl;
for(int i = lbk + 1; i <= rbk - 1; i++){
res += sum[i];
}
// cout<<res<<endl;
for(int i = st[rbk]; i <= r; i++){
res += a[i];
}
// cout<<res<<endl;
}
return res;
}
int main(){
ios_base::sync_with_stdio(false);
cin.tie(0);
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i ++){
cin>>a[i];
}
init(n);//初始化要崽读入之后
for(int i = 1; i <= m; i++){
int op, x, y;
cin >> op >> x >> y;
if(op == 1){
a[x]+=y;
sum[bl[x]]+=y;
}
else{
cout<<ask(x,y)<<endl;
}
}
}
树状数组
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N =5e5+5;
int a[N];
ll c[N];
int lowbit(int i){
return i &- i;
}
void add(int x, int y){
for(int i = x; i < N; i += i & -i){
c[i] += y;
}
}
ll ask(int x){
ll res =0;
for(int i = x; i; i -= i & -i){
res += c[i];
}
return res;
}
int main(){
int n,m;
cin >> n >> m;
for(int i=1;i<=n;i++){
int y;
cin >> y;
add(i, y);
}
while(m -- ){
int op,x,y;
cin >> op >> x >> y;
if(op == 1){
add(x , y);
}
else{
cout << ask(y) - ask(x-1) << endl;
}
}
}
差分树状数组
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N =5e5+5;
int a[N];
int df[N];
ll c[N];
int lowbit(int i){
return i &- i;
}
void add(int x, int y){
for(int i = x; i < N; i += i & -i){
c[i] += y;
}
}
ll ask(int x){
ll res =0;
for(int i = x; i; i -= i & -i){
res += c[i];
}
return res;
}
int main(){
int n,m;
cin >> n >> m;
for(int i=1;i<=n;i++){
cin >> a[i];
}
while(m -- ){
int op,x,y;
cin >> op ;
if(op == 1){
int k;
cin >> x >> y >> k;
add(x , k);
add(y+1 , -k);
}
else{
cin >> x;
cout << a[x] + ask(x) << endl;
}
}
}
线段树
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N =5e5+5;
struct tree{
int l, r;
ll dat;
}t[N*4];
int a[N];
void build(int p, int l, int r){
t[p].l = l, t[p].r = r;
if(l == r){
t[p].dat = a[l];
return;
}
int mid = l + r >> 1;
build(p * 2, l, mid);
build(p * 2 + 1, mid + 1, r);
t[p].dat = max(t[p*2].dat , t[p*2+1].dat);
}
void change(int p,int x, int v){
if(t[p].l == t[p].r){
t[p].dat += v;
return;
}
int mid = t[p].l + t[p].r >> 1;
if(x <= mid) change(p * 2, x, v);
else change(p * 2 + 1, x, v);
t[p].dat = max(t[p*2].dat , t[p*2+1].dat);
}
ll ask(int p, int l, int r){
if(l <= t[p].l && r >= t[p].r)return t[p].dat;
int mid = t[p].l + t[p].r >> 1;
ll val = 0;
if(l <= mid) val = max(val,ask(p*2, l, r));
if(r > mid) val = max(val,ask(p*2, l, r));
return val;
}
int main(){
ios_base::sync_with_stdio(false);
cin.tie(0);
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i ++){
cin >> a[i];
}
build(1, 1, n);
for(int i = 1; i <= m; i++){
int op, x, y;
cin >> op >> x >> y;
if(op == 1){
change(1, x, y);
}
else{
cout<<ask(1, x, y)<<endl;
}
}
}
图论
最小生成树
- 克鲁斯卡尔
void solve()
{
//求生成树,带个数权重的
int n, m;
cin >> n >> m;
vector<int> f(n + 1), s(n + 1, 1);
vector<vector<pii>> g(n + 1);
for (int i = 1; i <= n; i++)
f[i] = i;
auto find = [&](auto find, int i) -> int
{
if (i == f[i])
return i;
return f[i] = find(find, f[i]);
};
vector<pair<int, pii>> r;
for (int i = 1; i <= m; i++)
{
int a, b, c;
cin >> a >> b >> c;
r.push_back({c, {a, b}});
}
sort(r.begin(), r.end());
ll sum = 0;
for (auto i : r)
{
int a = i.second.first, b = i.second.second, c = i.first;
int fa = find(find, a), fb = find(find, b);
if (fa != fb)
{
f[fb] = fa;
s[fa] += s[fb];
sum += c;
}
}
if (s[find(find, 1)] == n)
cout << sum << "\n";
else
cout << "orz\n";
}
- 普林姆
链式前向星
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=1e5+5;//顶点数;
int h[N],e[N],ne[N],idx;
int v[N];
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
拓扑排序
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int h[N],e[N],ne[N],idx;
int d[N];//入度
int v[N];
vector<int>res;
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
//进阶版本
void tp(int n){//n个顶点;
queue<int>q;
for(int i=1;i<=n;i++){//首先把所有入度为0的点 放入队列之中;
if(d[i]==0)
q.push(i);
}
while(!q.empty()){//删掉所有入度为0的点的出边
int t=q.front();
q.pop();
res.push_back(t);
for(int i=h[t];i!=-1;i=ne[i]){ //遍历t的出边
int j=e[i];
if(--d[j]==0){
q.push(j);
}
}
}
if(res.size()!=n) cout<<"no!"; //判断是否有没输出的===>有环;
else{
for(int i:res) cout<<i<<" ";
}
}
二分图
染色法判定二分图
bool dfs(int x,int c){
color[x]=c;
for(int i=h[x];i!=-1;i=ne[i]){
int j=e[i];
if(!color[j]){
int m=dfs(j,3-c);
if(m)return true;
}
else{
if(c==color[j]){
return true;
}
}
}
return false;
}
匈牙利算法(二分图最大匹配)
时间复杂度 O ( n m ) O(nm) O(nm)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int h[505],match[505],st[505];
int e[N],ne[N],idx;
void add(int a,int b){
e[++idx]=b,ne[idx]=h[a],h[a]=idx;
}
bool find(int x){
for(int i=h[x];i;i=ne[i]){
int j=e[i];
if(!st[j]){
st[j]=1;
if(!match[j] || find(match[j])){
match[j]=x;
return true;
}
}
}
}
int main(){
int n1,n2,m;
cin>>n1>>n2>>m;
for(int i=1;i<=m;i++){
int a,b;
cin>>a>>b;
add(a,b);
}
int res=0;
for(int i=1;i<=n1;i++){
memset(st,0,sizeof st);
res+=find(i);
}
cout<<res;
}
最短路
双端队列求解01图
int dj(){
memset(v,0,sizeof v);
int end=(r+1)*(c+1);//终点
//双端队列求解;
deque<node>q;
q.push_back({1,0});
while(!q.empty()){
node t=q.front();
q.pop_front();
v[t.id]=1;//出来的时候才能标记 因为怕权重1的标记了丢后面 0的反而访问不了
if(t.id==end)return t.dis;
for(int i=h[t.id];i;i=ne[i]){
int j=e[i];
if(!v[j]){
if(w[i]==1){
q.push_back({j,t.dis+1});
}
else{
q.push_front({j,t.dis});
}
}
}
}
return -1;
}
朴素迪杰斯特拉
#include<bits/stdc++.h>
using namespace std;
const int N=505;
int inf=0x3f3f3f3f;
int r[N][N];
int n,m;
int v[N],dis[N];
int dj(){
memset(dis,0x3f,sizeof dis);
dis[1]=0;//表示起点是1
for(int i=1;i<=n;i++){//执行n次 固定
int id=-1;
for(int j=1;j<=n;j++){
if(!v[j] && (id==-1 || dis[id]>dis[j])){
id=j;
}
}
v[id]=1;//根据这点进行松弛
for(int j=1;j<=n;j++){
dis[j]=min(dis[j],dis[id]+r[id][j]);
}
}
if(dis[n]>inf/2)return -1;
return dis[n];
}
int main(){
memset(r,0x3f,sizeof r);
cin>>n>>m;
for(int i=1;i<=m;i++){
int a,b,c;
cin>>a>>b>>c;
r[a][b]=min(r[a][b],c);
}
cout<<dj();
}
优先队列优化迪杰斯特拉
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
typedef long long ll;
const int inf =0x3f3f3f3f;
int n,m;
int e[N],h[N],w[N],ne[N],idx,v[N],dis[N];
void add(int a,int b,int c){
e[++idx]=b,w[idx]=c;
ne[idx]=h[a],h[a]=idx;
}
struct node{
int id,dis;
bool operator < (const node b )const {
return dis>b.dis;
}
};
int dj(){
memset(dis,0x3f,sizeof dis);
priority_queue<node,vector<node>>q;
q.push({1,0});
while(!q.empty()){
auto t=q.top();
q.pop();
if(v[t.id])continue;
v[t.id]=1;//一定要在取出来的时候才能固定 因为放进去只代表被更新
for(int i=h[t.id];i;i=ne[i]){
int j=e[i];
if(!v[j] && dis[j]>t.dis+w[i]){
dis[j]=t.dis+w[i];
q.push({j,dis[j]});
}
}
}
if(dis[n]>inf/2)return -1;
return dis[n];
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
}
cout<<dj();
}
bellman-ford
适用于有边限制的最短路,也适用于负权图
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=505;
int n,m,k;
int dis[N];
struct node{
int a,b,c;
}r[10005];
void bellman_ford(){
memset(dis,0x3f,sizeof dis);
dis[1]=0;
int cp[N]={};
for(int i=1;i<=k;i++){
memcpy(cp,dis,sizeof dis);
for(int j=1;j<=m;j++){
int a=r[j].a,b=r[j].b,c=r[j].c;
if(cp[b]>cp[a]+c)dis[b]=cp[a]+c;
}
}
if(dis[n]>0x3f3f3f3f/2)cout<<"impossible"<<endl;
else cout<<dis[n]<<endl;
}
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;++i){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
r[i]={a,b,c};
}
bellman_ford();
}
SPFA
void spfa(){
memset(dis,0x3f,sizeof dis);
queue<int>q;
q.push(1);
dis[1]=0;
while(!q.empty()){
int t=q.front();
q.pop();
v[t]=0;//用来是否已在队列里面,如果已经在了,就不要反复添加。 可以提高效率
for(int i=h[t];i;i=ne[i]){
int j=e[i];
if(dis[j]>dis[t]+w[i]){
dis[j]=dis[t]+w[i];
if(!v[j])q.push(j),v[j]=1;
}
}
}
if(dis[n]>inf/2)cout<<"impossible";
else cout<<dis[n];
}
- 判断负环
void spfa(){
memset(dis,0x3f,sizeof dis);
queue<int>q;
for(int i=1;i<=n;i++){
q.push(i);
v[i]=1;
}
while(!q.empty()){
int t=q.front();
q.pop();
v[t]=0;
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(w[i]+dis[t]<dis[j]){
dis[j]=w[i]+dis[t];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n){ //用cnt记录,最多被优化n次,不可能一直被优化
cout<<"Yes";
return ;
}
if(!v[j]){
q.push(j);
v[j]=1;
}
}
}
}
cout<<"No";
}
Floyd
memset(r,0x3f,sizeof r);
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(i==j)r[i][j]=0;
else r[i][j]=min(r[i][j],r[i][k]+r[k][j]);
}
}
}