【古谷彻】算法模板(更新ing···)

本文详细介绍了算法的基础知识,包括数学部分的逆元、组合数、高精度sqrt等,数据结构中的并查集、线段树、树状数组等,动态规划的背包问题和状态压缩,以及图论相关概念。此外,还涵盖了输入输出优化、字符串处理、STL容器的使用和位运算技巧。是算法学习者的实用参考资料。
摘要由CSDN通过智能技术生成

目录

一、数学

1、逆元

(一)费马小定理/欧拉定理(快速幂)

2、组合数

(1)求组合数C(n,m)

方法一:阶乘+逆元+快速幂求组合数

方法二:记忆化搜索

方法三:递推公式

(2)组合数求概率 

3、高精度sqrt

 (1)二分法

(2)递加递减

4、快速幂

5、欧拉函数

 方法一:埃氏筛

方法二:欧拉筛

6、 质数筛

方法一:欧拉筛

7、质数判断

8、欧拉常数

9、线性基 

形式一:数组

1、处理线性基

2、最大异或和

3、最小异或和

形式二:容器 

10.二分 

二、数据结构 

1、并查集

(1)普通并查集

优化一:路径压缩(会破坏树结构!!!

优化二:启发式合并(不压缩路径,保持树结构!!!

形式1:按高合并

形式2:按集合大小合并

优化三: 压缩路径+启发式合并

(2)带权并查集

(3)种类并查集——维护循环对称关系 

两种关系:

三种关系: 

(3)可撤销并查集(看不懂。。。 

2、启发式合并

3、字符串哈希

形式一:单哈希

4、线段树 

(1)单点修改,区间查询

(2)区间修改,区间查询

5、线段树进阶

(1)线段树区间染色 

6、树状数组 

(1)单点修改,区间查询

(2)区间修改,单点查询 

(3)求逆序对数量

7、分块 

8、树链剖分

9、RMQ问题(区间最值)

三、动态规划

1、背包问题

(1)0-1背包问题

(2)完全背包问题 

(3)多重背包问题

(4)混合背包问题 

(5) 分组背包问题

2、 状态压缩dp

四、图论

1、图的存储

(1)邻接矩阵

(2)邻接表

(3)链式前向星

2、二分图最大匹配

 (1)匈牙利算法

方法一:邻接矩阵

方法二:链式前向星(注意数组及边的标号问题!!!) 

(2)KM算法 

3、最小点覆盖

4、最短路

(1)Dijkstra算法

方法一:链式前向星+优先队列

(2)Floyd算法 

(3)Bellmon-Ford算法 

(4)SPFA算法 

5、拓扑排序 

6、完全N叉树 

(1)完全N叉树的性质

1)编号从0开始

A、结点间编号关系

2)编号从1开始

A、结点间编号关系

五、字符串

六、STL

1、优先队列—priority_queue

2、双端队列——deque

3、集合——set/multiset

七、输入与输出

1、提高效率

(1)关闭同步

(2)开启O优化

2、大数处理

(1)__int128

八、位运算 

1、基本操作

九、方法和结论 

1、最小表示法

2、伯特兰-切比雪夫(Bertrand-Chebyshev)定理

3、素数定理

4、哥德巴赫猜想

5、差分

6、离散化

方法一

方法二

7、倍增

8、两点之间的距离 

(1)欧氏距离(欧几里得距离)

(2)曼哈顿距离

(3)切比雪夫距离

9、关于memset函数 

10、二进制枚举

11、二进制拆分(多重背包问题)

12、常用函数

(1)全排列函数——next_permutation

(2)复制子字符串函数——substr

(3)string中的insert函数

 13、鸽巢原理(抽屉原理)

14.模板 


一、数学

1、逆元

以ax(mod p)=1为例

(一)费马小定理/欧拉定理(快速幂)

p是质数且a不能被p整除

 

#include<iostream>
#define int long long
using namespace std;

const int p=998244353;
int power(int a,int b,int p){
	int ans=1;
	while(b){
		if(b&1){
			ans=ans*a%p;
		}
		b>>=1;
		a=a*a%p;
	}
	return ans;
}

signed main(){
	int a;
	cin>>a;
	if(a%p==0){
		cout<<-1<<'\n';				//a可以被p整除
	}else{
		cout<<power(a,p-2,p)<<'\n';//求a关于p的逆元
	}
	return 0;
}

2、组合数

(1)求组合数C(n,m)

方法一:阶乘+逆元+快速幂求组合数

数较大时对阶乘进行初始化,直接用阶乘计算组合数

#include<iostream>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
using namespace std;

const int N=1000005;
const int mod=998244353;
int inv[N],fac[N];

int power(int a,int b){
    int ans=1;
    while(b){
        if(b&1) ans=ans*a%mod;
        b>>=1;
        a=a*a%mod;
    }
    return ans;
}

void init(){
    fac[0]=inv[0]=1;
    for(int i=1;i<N;i++){
        fac[i]=fac[i-1]*i%mod;
    }
    inv[N-1]=power(fac[N-1],mod-2);
    for(int i=N-2;i;i--){
        inv[i]=inv[i+1]*(i+1)%mod;
    }
}

int C(int n,int m){
    if(n<m) return 0;
    return fac[n]*inv[m]%mod*inv[n-m]%mod;
}

signed main(){
    IOS;
    init();
    int T;  cin>>T;
    while(T--){
       
    }
    return 0;
}
方法二:记忆化搜索

只适合求特定一个组合数,不适合用于初始化

int C(int n,int m){
    if(n<m) return 0;
    if(!m||m==n) return 1;
    if(C_[n][m]) return C_[n][m];
    return C_[n][m]=(C(n-1,m)+C(n-1,m-1))%mod;
}
方法三:递推公式

可用于较小数的初始化,较大的数时会爆栈

void C(int N){
    for(int i=0;i<N;i++){
        C[i][0]=C[i][i]=1;
    }
    for(int i=1;i<N;i++){
        for(int j=1;j<i;j++){
            C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
        }
    }
}

(2)组合数求概率 

共有n场比赛,每场比赛的胜率均为a/b,每次获胜得到k分,求总得分的期望值

(\frac{a}{b})^{i}*(\frac{b-a}{b})^{n-i},即\frac{a^{i}*(b-a)^{n-i}}{b^{n}}

#include<iostream>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
using namespace std;

const int N=1000005;
const int mod=998244353;
int inv[N],fac[N];

int power(int a,int b){
    int ans=1;
    while(b){
        if(b&1) ans=ans*a%mod;
        b>>=1;
        a=a*a%mod;
    }
    return ans;
}

void init(){
    fac[0]=inv[0]=1;
    for(int i=1;i<N;i++){
        fac[i]=fac[i-1]*i%mod;
    }
    inv[N-1]=power(fac[N-1],mod-2);
    for(int i=N-2;i;i--){
        inv[i]=inv[i+1]*(i+1)%mod;
    }
}

int C(int n,int m){
    if(n<m) return 0;
    return fac[n]*inv[m]%mod*inv[n-m]%mod;
}

signed main(){
    IOS;
    init();
    int T;  cin>>T;
    while(T--){
        int n,a,b,k; cin>>n>>a>>b>>k;
        int win=a,lose=power(b-a,n-1),invba=power(b-a,mod-2),invbn=power(power(b,n),mod-2),ans=0;
        for(int i=1;i<=n;i++){
            int pro=win*lose%mod*invbn%mod;
            ans=(ans+pro*C(n,i)%mod*k%mod)%mod;
            win=win*a%mod;
            lose=lose*invba%mod;
            k=(k+k)%mod;
        }
        cout<<ans<<'\n';
    }
    return 0;
}

3、高精度sqrt

 (1)二分法

int safe_sqrt(int x){
    int l=0,r=2*sqrtl(x),mid;
    while(l<=r){
        mid=(l+r)>>1;
        if(mid*mid>=x){
            r=mid-1;
        }else{
            l=mid+1;
        }
    }
    return l;
}

(2)递加递减

int safe_sqrt(int x){
    int ret=sqrtl(x);
    while((ret+1)*(ret+1)<=a) ret++;
    while(ret*ret>a) ret--;
    return ret;
}

4、快速幂

int power(int a,int b,int mod){
	int ans=1;
	while(b){
		if(b&1){
			ans=ans*a%mod;
		}
		b>>=1;
		a=a*a%mod;
	} 
	return ans;
}

5、欧拉函数

 方法一:埃氏筛

const int N=200005;
int phi[N];
void euler(int n){
	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);
			}
		}
	}
} 

方法二:欧拉筛

const int N=200005;
int phi[N],num[N];
bool isnp[N];
void euler(int n){
	int ant=0;
	phi[1]=1;
	for(int i=2;i<=n;i++){
		if(!isnp[i]){
			num[++ant]=i;
			phi[i]=i-1;
		}
	}
	for(int j=1;j<=ant&&i*num[j]<=n;j++){
		isnp[i*num[j]]=1;
		if(!i%num[j]){
			phi[i*num[j]]=phi[i]*num[j];
			break;
		}
		phi[i*num[j]]=phi[i]*phi[num[j]];
	}
}

6、 质数筛

方法一:欧拉筛

初始化1—N的素数

void euler()
{
    for (int i = 2; i <= N; i++)
    {
        if (!isnp[i])
            prim[++cnt] = i;
        for (int j = 1; j <= cnt && i * prim[j] <= N; j++)
        {
            isnp[i * prim[j]] = 1;
            if (i % prim[j] == 0)
                break;
        }
    }
}

7、质数判断

bool prime(int n){
	if(n==2||n==3){
		return 1;
	}
	if(n%6!=1&&n%6!=5){
		return 0;
	}
	for(int i=5;i*i<=n;i+=6){
		if(n%i==0||n%(i+2)==0){
			 return 0;
		}
	}
	return 1;	
}

8、欧拉常数

求1+1/2+1/3+1/4+1/5+1/6+...1/n

#include<isotream>
#include<cmath>
#define int long long
using namespace std;

const double euler=0.57721566490153286060651209;
signed main(){
    int n; cin>>n;
    double ans=euler+log(n);
    return 0;
}

9、线性基 

形式一:数组

1、处理线性基
bool add(int x){
    for(int i=62;i>=0;i--){
        if(x>>i){
            if(b[i]){
                x^=b[i];
            }else{
                b[i]=x;
                return 1;
            }
        }
    }
    return 0;
}
2、最大异或和
int maxx(){
    int ans=0;
    for(int i=62;i>=0;i--){
        ans=max(ans,ans^b[i]);
    }
    return ans;
}
3、最小异或和
0或者最小的d[i]

形式二:容器 

vector<int> b;
void add(int x){
    for(auto i:b){
        x=min(x,b^x);
    }
    for(auto &i:b){
        i=min(i,i^x);
    }
    if(x){
        b.push_back(x);
    }
}

10.二分 

// 寻找最小值
#include <iostream>
using namespace std;

int main()
{
    int l = 1, r = 1e9, mid, ans;
    while (l <= r)
    {
        mid = l + r >> 1;
        if (check(mid))
        {
            ans = mid;
            r = mid - 1;
        }
        else
        {
            l = mid + 1;
        }
    }
    cout << ans;
}

// 寻找最大值
#include <iostream>
using namespace std;

int main()
{
    int l = 1, r = 1e9, mid, ans;
    while (l <= r)
    {
        mid = l + r >> 1;
        if (check(mid))
        {
            ans = mid;
            l = mid + 1;
        }
        else
        {
            r = mid - 1;
        }
    }
    cout << ans;
}

二、数据结构 

1、并查集

(1)普通并查集

优化一:路径压缩(会破坏树结构!!!
#include<iostream>
using namespace std;

const int N=200005;
int fa[N];
int n,m;

void init(){
    for(int i=1;i<=n;i++){
        fa[i]=i;
    }
} 

int find(int x){
    return fa[x]==x?x:(fa[x]=find(fa[x]));
}

void merge(int x,int y){
    fa[find(x)]=find(y);
}

int main(){
    cin>>n>>m;
    init();
    for(int i=1;i<=m;i++){
        int u,v; cin>>u>>v;
        merge(u,v);
    }
    return 0;
}
优化二:启发式合并(不压缩路径,保持树结构!!!
形式1:按高合并
#include<iostream>
using namespace std;

const int N=200005;
int fa[N],ra[N];
int n,m;

void init(){
    for(int i=1;i<=n;i++){
        fa[i]=i;
        ra[i]=1;
    }
}

int find(int x){
    return fa[x]==x?x:find(fa[x]);
}

void merge(int x,int y){
    int fx=find(x),fy=find(y);
    if(fx==fy) return;
    if(ra[fx]>ra[fy]){
        fa[fy]=fx;
    }else{
        fa[fx]=fy;
        if(ra[fx]==ra[fy]) ra[fy]++;
    }
}

int main(){
    cin>>n>>m;
    init();
    for(int i=1;i<=m;i++){
        int u,v; cin>>u>>v;
        merge(u,v);
    }
    return 0;
}
形式2:按集合大小合并
#include<iostream>
using namespace std;

const int N=200005;
int fa[N],sz[N];
int n,m;

void init(){
    for(int i=1;i<=n;i++){
        fa[i]=i;
        sz[i]=1;
    }
}

int find(int x){
    return fa[x]==x?x:(fa[x]=find(fa[x]));
}

void merge(int x,int y){
    int fx=find(x),fy=find(y);
    if(fx==fy) return;
    if(sz[fx]>sz[fy]){
        sz[fx]+=sz[fy];
        fa[fy]=fx;
    }else{
        sz[fy]+=sz[fx];
        fa[fx]=fy;
    }
}

int main(){
    cin>>n>>m;
    init();
    for(int i=1;i<=m;i++){
        int u,v; cin>>u>>v;
        merge(u,v);
    }
    return 0;
}
优化三: 压缩路径+启发式合并
#include<iostream>
using namespace std;

const int N=200005;
int fa[N],ra[N];
int n,m;

void init(){
    for(int i=1;i<=n;i++){
        fa[i]=i;
        ra[i]=1;
    }
} 

int find(int x){
    return fa[x]==x?x:(fa[x]=find(fa[x]));
}

void merge(int x,int y){
    int fx=find(x),fy=find(y);
    if(fx==fy) return;
    if(ra[fx]>ra[fy]){
        fa[fy]=fx;
    }else{
        fa[fx]=fy;
        if(ra[fx]==ra[fy]) ra[fy]++;
    }
}

int main(){
    cin>>n>>m;
    init();
    for(int i=1;i<=m;i++){
        int u,v; cin>>u>>v;
        merge(u,v);
    }
    return 0;
}

(2)带权并查集

(3)种类并查集——维护循环对称关系 

两种关系:
#include<iostream>
#include<algorithm>
using namespace std;

const int N=200005;
int n,m; 
int fa[N],ra[N];
struct Node{
    int u;
    int v;
    int w;
}a[N];

bool cmp(Node x,Node y){
    return x.w>y.w;
}

void init(){
    for(int i=1;i<=2*n;i++){
        fa[i]=i;
        ra[i]=1;
    }
} 

int find(int x){
    return fa[x]==x?x:(fa[x]=find(fa[x]));
}

void merge(int x,int y){
    int fx=find(x),fy=find(y);
    if(fx==fy) return;
    if(ra[fx]>ra[fy]){
        fa[fy]=fx;
    }else{
        fa[fx]=fy;
        if(ra[fx]==ra[fy]) ra[fy]++;
    }
}

int main(){
    cin>>n>>m;
    init();
    for(int i=0;i<m;i++){
        cin>>a[i].u>>a[i].v>>a[i].w;
    }
    sort(a,a+m,cmp);
    for(int i=0;i<m;i++){
        if(find(a[i].u)==find(a[i].v)){
            cout<<a[i].w;
            break;
        }
        merge(a[i].u,a[i].v+n);
        merge(a[i].u+n,a[i].v);
        if(i==m-1){
            cout<<"0";
        }
    }
    return 0;
}
三种关系: 
#include<iostream>
using namespace std;

const int N=200005;
int fa[N],ra[N];
int n,m;

void init(){
    for(int i=1;i<=n*3;i++){
        fa[i]=i;
        ra[i]=1;
    }
} 

int find(int x){
    return fa[x]==x?x:(fa[x]=find(fa[x]));
}

void merge(int x,int y){
    int fx=find(x),fy=find(y);
    if(fx==fy) return;
    if(ra[fx]>ra[fy]){
        fa[fy]=fx;
    }else{
        fa[fx]=fy;
        if(ra[fx]==ra[fy]) ra[fy]++;
    }
}

int main(){
    int ans=0;
    cin>>n>>m;
    init();
    for(int i=0;i<m;i++){
        int op,x,y; cin>>op>>x>>y;
        if(x>n||y>n){
            ans++;
        }else if(op==1){
            if(find(x)==find(y+n)||find(x)==find(y+2*n)){
                ans++;
            }else{
                merge(x,y);                 
                merge(x+n,y+n);       
                merge(x+2*n,y+2*n);
            }
        }else if(op==2){
            if(find(x)==find(y)||find(x)==find(y+2*n)){
                ans++;
            }else{
                merge(x,y+n);      
                merge(x+n,y+2*n); 
                merge(x+2*n,y);   
            }
        }
    }
    cout<<ans<<'\n';
    return 0;
}

(3)可撤销并查集(看不懂。。。 

#include<iostream>
#include<vector>
using namespace std;

const int N=200005;
typedef pair<int,int> PII;
int fa[N],sz[N];
vector<PII>his_fa;
vector<PII>his_sz;
int n,m;

void init(){
    for(int i=1;i<=n;i++){
        fa[i]=i;
        sz[i]=1;
    }
}

int find(int x){
    return fa[x]==x?x:(fa[x]=find(fa[x]));
}

void merge(int x,int y){
    int fx=find(x),fy=find(y);
    if(fx==fy) return;
    if(sz[fx]>sz[fy]){
        his_sz.push_back({sz[fx],sz[fx]});
        sz[fx]+=sz[fy];
        his_fa.push_back({fa[fy],fa[fy]});
        fa[fy]=fx;
    }else{
        his_sz.push_back({sz[fy],sz[fy]});
        sz[fy]+=sz[fx];
        his_fa.push_back({fa[fx],fa[fx]});
        fa[fx]=fy;
    }
}

void roll(int h){
    while(his_fa.size()>h){
        his_fa.back().first=his_fa.back().second;
        his_fa.pop_back();
        his_sz.back().first=his_sz.back().second;
        his_sz.pop_back();
    }
}

int main(){
    cin>>n>>m;
    init();
    for(int i=1;i<=m;i++){
        int u,v; cin>>u>>v;
        merge(u,v);
    }
    return 0;
}

2、启发式合并

小集合合并到大集合!!!

void merge(vector<int>&a,vector<int>&b){
    if(a.size()>b.size()){
        for(auto v:b) a.push_back(b);
    }else{
        for(auto v:a) b.push_back(a);
    }
}

3、字符串哈希

        Base        Mod

        131         1e9+7

        233         1e9+7

        31          666623333

形式一:单哈希

for(int j=0;j<m;j++){
    a[i]=(a[i]*base+s[j]-'a'+1)%mod;
}

4、线段树 

(1)单点修改,区间查询

#include<iostream>
#include<cstring>
using namespace std;

const int N=50005;
int a[N],sum[N<<2];

void push_up(int root){
	int rt=root<<1;
	sum[root]=sum[rt]+sum[rt|1];
}

void build(int root,int l,int r){
	if(l==r){
		sum[root]=a[l];
		return;
	}
	int rt=root<<1,mid=l+r>>1;
	build(rt,l,mid);
	build(rt|1,mid+1,r);
	push_up(root);
}

void update(int root,int l,int r,int pos,int val){
	if(l==r){
		sum[root]+=val;
		return;
	}
	int rt=root<<1,mid=l+r>>1;
	if(pos<=mid){
		update(rt,l,mid,pos,val);
	}else{
		update(rt|1,mid+1,r,pos,val); 
	}
	push_up(root);
}

int query(int root,int l,int r,int ql,int qr){
	if(ql<=l&&r<=qr){
		return sum[root];
	}
	push_down(root,l,r);
	int rt=root<<1,mid=l+r>>1,ans=0;
	if(ql<=mid){
		ans+=query(rt,l,mid,ql,qr);
	}
    if(mid<qr){
		ans+=query(rt|1,mid+1,r,ql,qr);
	}
	return ans;
}

int main(){
    int T; cin>>T;
    for(int i=1;i<=T;i++){
		memset(sum,0,sizeof(sum));
		memset(lazy,0,sizeof(lazy));
        int n; cin>>n;
        for(int j=1;j<=n;j++){
            cin>>a[j];
        }
        build(1,1,n);
		printf("Case %d:\n",i);
        while(1){
            string s; cin>>s;
            if(s[0]=='E'){
				break;
			}
			int l,r; cin>>l>>r;
            if(s[0]=='A'){
                update(1,1,n,l,l,r);
            }else if(s[0]=='S'){
                update(1,1,n,l,l,-r);
            }else{
                cout<<query(1,1,n,l,r)<<'\n';
            }
        }
    }
	return 0;
}

(2)区间修改,区间查询

#include<iostream>
#include<cstring>
using namespace std;

const int N=50005;
int a[N],sum[N<<2],lazy[N<<2];

void push_up(int root){
	int rt=root<<1;
	sum[root]=sum[rt]+sum[rt|1];
}

void push_down(int root,int l,int r){
	if(lazy[root]){
		int rt=root<<1,mid=l+r>>1;
		lazy[rt]+=lazy[root];
		lazy[rt|1]+=lazy[root];
		s
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

古谷彻

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值