基础算法模板(Markdown重写方便查询)

文章目录

  • 扩展欧几里得
  • 矩阵快速幂 (O(x^3^nlogn)x为构造矩阵阶数)
  • 米勒罗宾素数检测
  • __int128
  • 拓扑排序O(n^3^)
  • 树的直径
  • 归并排序 O(nlogn)
  • LCS记录路径 O(n*m)
  • Dijkstra求单源最短路
  • KMP
  • tarjan+缩点
  • 网络流求最小割即最大流
      • EK
      • dinic
  • tarjan求lca

扩展欧几里得

ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if (!b)
    {
        x = 1,y = 0;
        return a;
    }
    int r = exgcd(b,a%b,x,y);
    int tmp = y;
    y = x - (a / b) * y;
    x = tmp;
    return r;
}

矩阵快速幂 (O(x3nlogn)x为构造矩阵阶数)

以hduoj6470为例, f n = f n − 1 + 2 f n − 2 + n 3 fn = f_{n-1} + 2f_{n-2} + n^3 fn=fn1+2fn2+n3

#include
using namespace std;
#define ll long long
const ll mod = 123456789;
const int N = 6;
struct Matrix
{
    int n;
    ll d[6][6];
    void init(int n)
    {
        this -> n = n;
        memset(d,0,sizeof(d));
    }
    Matrix operator *(Matrix &b)
    {
        Matrix ans;
        ans.init(n);
        for (int i = 0;i < n;i ++)
            for (int j = 0;j < n;j ++)
                for (int k = 0;k < n;k ++)
                    ans.d[i][j]=(ans.d[i][j]+d[i][k]*b.d[k][j])%mod;
        return ans;
    }
};
ll a[N][N] = {
        {1,1,0,0,0,0},
        {2,0,0,0,0,0},
        {1,0,1,0,0,0},
        {3,0,3,1,0,0},
        {3,0,3,2,1,0},
        {1,0,1,1,1,1}
        };
ll b[6] = {2,1,8,4,2,1};
Matrix quick(Matrix a,ll b)
{
    Matrix res;
    res.init(a.n);
    for (int i = 0;i < res.n;i ++)
        res.d[i][i] = 1;
    while (b)
    {
        if (b & 1) res = res * a;
        a = a * a;
        b >>= 1;
    }
    return res;
}
int main()
{
    ll t,n;
    scanf("%lld",&t);
    while (t --)
    {
        scanf("%lld",&n);
        if (n < 2)
        {
            printf("%lld\n",n);
            continue;
        }
        Matrix x,ans;
        x.init(6),ans.init(6);
        for (int i = 0;i < N;i ++)
            for (int j = 0;j < N;j ++)
                x.d[i][j] = a[i][j];
        for (int i = 0;i<N;i++) ans.d[0][i]=b[i];
        x = quick(x,n - 1);
        ans = ans * x;
        printf("%lld\n",ans.d[0][1]);
    }
    return 0;
}

米勒罗宾素数检测

ll ksc(ll a,ll b,ll p)
{
    return (a*b-(ll)((long double)a/p*b)*p+p)%p;
}
ll prime[5] = {2, 5, 3, 233, 331};
ll qpow(ll a,ll b,ll p)
{
    ll ans = 1;
    while (b)
    {
        if (b & 1) ans = ksc(ans,a,p);
        b >>= 1;
        a = ksc(a,a,p);
    }
    return ans;
}
bool Miller_Rabin(ll p)
{
    if(p < 2) return 0;
    if(p != 2 && p % 2 == 0) return 0;
    ll s = p - 1;
    while(! (s & 1)) s >>= 1;
    for(int i = 0; i < 3; ++i)
    {
        if(p == prime[i]) return 1;
        ll t = s, m = qpow(prime[i], s, p);
        while(t != p - 1 && m != 1 && m != p - 1)
        {
            m = ksc(m, m, p);
            t <<= 1;
        }
        if(m != p - 1 && !(t & 1)) return 0;
    }
    return 1;
}

__int128

读入输出

inline __int128 read()
{
   int X=0,w=0; char ch=0;
   while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
   while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
   return w?-X:X;
}
inline void print(__int128 x)
{    
   if(x<0){putchar('-');x=-x;}
   if(x>9) print(x/10);
   putchar(x%10+'0');
}

拓扑排序O(n3)

const int maxn = (int)5e2 + 10;
int G[maxn][maxn];
int vis[maxn];
int n,m;
void toposort()
{
	for (int i = 1;i <= n;i ++)
	{
		for (int j = 1;j <= n;j ++)
		{
			if (!vis[j])
			{
				printf("%d",j);
				vis[j]--;
				if (i != n) putchar(' ');
				else  putchar('\n');
				for (int k = 1;k <= n;k ++)
				{
					if (G[j][k]) vis[k]--;
				}
				break;
			}
		}
	}
}

树的直径

两次bfs,一次找底部节点,一次找直径,以poj2631为例

#include
#include
#include
#include
#include
#define mem(a) memset(a,0,sizeof(a))
using namespace std;
const int maxn = (int)1e4 + 10;
vector<pair<int,int> > G[maxn];
int dis[maxn],ans;
bool vis[maxn];
int bfs(int x)
{
	mem(dis);
	mem(vis);
	queue<int> q;
	q.push(x);
	int p = 0;
	vis[x] = 1,dis[x] = 0;
	while (!q.empty())
	{
		int t = q.front();
		q.pop();
		if (dis[t] > ans)
		{
			ans = dis[t];
			p = t;
		}
		pair<int,int> r;
		for (int i = 0;i < G[t].size();i ++)
		{
			r = G[t][i];
			if (!vis[r.first])
			{
				vis[r.first] = 1;
				dis[r.first] = dis[t] + r.second;
				q.push(r.first);
			}
		 }
	}
	return p;
}
int main()
{
	int x,y,z;
	while (cin>>x>>y>>z)
	{
		G[x].push_back(make_pair(y,z));
		G[y].push_back(make_pair(x,z));	
	}
	int point = bfs(1);
	ans = 0;
	bfs(point);
	cout<<ans<<endl;
	return 0;
}

归并排序 O(nlogn)

const int maxn = (int)5e5 + 10;
int a[maxn],temp[maxn];
void merge_sort(int l,int r)
{
	int mid = (l + r) >> 1;
	if (l == r)
		return ;
	for (int i = l,j = mid + 1,pos = 0;i <= mid || j <= r;pos ++)
	{
		if (i > mid) temp[pos] = a[j ++];
		else if (j > r) temp[pos] = a[i ++];
		else  if (a[i] <= a[j]) temp[pos] = a[i ++];
		else
		{
			temp[pos] = a[j ++];
		}
	}
	for (int i = 0;i <= r - l;i ++)
		a[l + i] = temp[i];
}

LCS记录路径 O(n*m)

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ll long long
#define inf 0x3f3f3f3f
#define mem(a) memset(a,0,sizeof(a))
using namespace std;
char a[1005],b[1005],c[1005];
int dp[1005][1005];
int main()
{
	scanf("%s %s",a,b);
	int n = strlen(a),m = strlen(b);
	dp[0][0] = 0;
	for (int i = 0;i < n;i ++)
		for (int j = 0;j < m;j ++)
		{
			if (a[i] == b[j])
				dp[i + 1][j + 1] = dp[i][j] + 1;
			else
				dp[i + 1][j + 1] = max(dp[i][j + 1],dp[i + 1][j]);
		}
	int pos = 0;
	while (dp[n][m])
	{
		if (dp[n - 1][m] == dp[n][m])
			n --;
		else if (dp[n][m - 1] == dp[n][m])
			m --;
		else
			n--,m--,c[pos ++] = a[n];
	}	
	while (pos) printf("%c",c[--pos]);
	putchar('\n');
	return 0;
}

Dijkstra求单源最短路

链式前向星建图O(E * logN) 以HDUoj1874为例:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ll long long
#define inf 0x3f3f3f3f
#define mem(a) memset(a,-1,sizeof(a))
using namespace std;
typedef pair<int,int> par;
const int maxn = 110;
const int MAXN = 1010;
int n,m;
//链式前向星建图 
struct Edge
{
	int to,next,val;
	Edge(){}
	Edge(int _to,int _next,int _val)
	{
		to = _to,next = _next,val = _val;
	}
}edge[MAXN << 1];
//初始化 
int head[maxn],top;
void init(int n)
{
	memset(head,-1,sizeof(int) * (n + 1));
	top = 0;
}
//边 
void add(int u,int v,int val)
{
	edge[top] = Edge(v,head[u],val);
	head[u] = top ++;
}
void getmap(int m)
{
	int u,v,val;
	while (m --)
	{
		scanf("%d %d %d",&u,&v,&val);
		add(u,v,val);
		add(v,u,val);//双向图
	}
}

int dis[maxn];
void djk(int st,int end) //核心代码dijkstra算法 
{
	memset(dis,0x3f,sizeof(int) * (n + 1));
	priority_queue<par,vector<par>,greater<par> > que;
	dis[st] = 0,que.push(make_pair(0,st));
	while (!que.empty())
	{
		par p = que.top();
		que.pop();
		int v = p.second;
		if (dis[v] < p.first) continue;
		for (int i = head[v]; ~i ;i = edge[i].next)	
		{
			Edge e = edge[i];
			if (dis[e.to] > dis[v] + e.val)
			{
				dis[e.to] = dis[v] + e.val;
				que.push(make_pair(dis[e.to],e.to)); 
			}
		}
	}
	printf("%d\n",dis[end] == inf ? -1 : dis[end]); 
}
int main()
{
	int s,t;
	while (~scanf("%d %d",&n,&m))
	{
		init(n);
	 	getmap(m);
	 	scanf("%d %d",&s,&t);
	 	djk(s,t);
	}
	return 0;
}

KMP

O(n+m)

const int maxn = (int)1e6 + 10;
const int MAXN = (int)1e4 + 10;
int f[maxn];//母串 
int s[MAXN];//子串 
int nxt[MAXN];//预处理最长前后缀 
int n,m;
void init()
{
	int i = 1,j = 0;
	nxt[0] = 0;
	while (i < m)
	{
		if (s[i] == s[j])
			nxt[i ++] = ++ j;
		else if (!j)
			i ++;
		else
			j = nxt[j - 1];
	}
}
int kmp()
{
	int i = 0,j = 0;
	while (i < n && j < m)
	{
		if (f[i] == s[j])
		{
			i ++;
			j ++;
		}
		else if (!j)
			i ++;
		else
			j = nxt[j - 1];
	}
	if (j == m)
		return i - m + 1;//返回第一次匹配的首字母位置 
	return -1;//无匹配 
}

tarjan+缩点

#define N 30100
//N为最大点数
#define M 150100
//M为最大边数
int n, m;//n m 为点数和边数
struct Edge{
	int from, to, nex;
	bool sign;//是否为桥
}edge[M<<1];
int head[N], edgenum;
void add(int u, int v){//边的起点和终点
	Edge E={u, v, head[u], false};
	edge[edgenum] = E;
	head[u] = edgenum++;
}
int DFN[N], Low[N], Stack[N], top, Time; //Low[u]是点集{u点及以u点为根的子树} 中(所有反向弧)能指向的(离根最近的祖先v) 的DFN[v]值(即v点时间戳)
int taj;//连通分支标号,从1开始
int Belong[N];//Belong[i] 表示i点属于的连通分支
bool Instack[N];
vector<int> bcc[N]; //标号从1开始

void tarjan(int u ,int fa){
	DFN[u] = Low[u] = ++ Time ;
	Stack[top ++ ] = u ;
	Instack[u] = 1 ;

	for (int i = head[u] ; ~i ; i = edge[i].nex ){
		int v = edge[i].to ;
		if(DFN[v] == -1)
		{
			tarjan(v , u) ;
			Low[u] = min(Low[u] ,Low[v]) ;
			if(DFN[u] < Low[v])
			{
				edge[i].sign = 1;//为割桥
			}
		}
		else if(Instack[v]) Low[u] = min(Low[u] ,DFN[v]) ;
	}
	if(Low[u] == DFN[u]){
		int now;
		taj ++ ; bcc[taj].clear();
		do{
			now = Stack[-- top] ;
			Instack[now] = 0 ;
			Belong [now] = taj ;
			bcc[taj].push_back(now);
		}while(now != u) ;
	}
}
void tarjan_init(int all){
	memset(DFN, -1, sizeof(DFN));
	memset(Instack, 0, sizeof(Instack));
	top = Time = taj = 0;
	for(int i=1;i<=all;i++)if(DFN[i]==-1 )tarjan(i, i); //注意开始点标!!!
}
vector<int>G[N];
int du[N];
void shk(){
	memset(du, 0, sizeof(du));
	for(int i = 1; i <= taj; i++)G[i].clear();
	for(int i = 0; i < edgenum; i++){
		int u = Belong[edge[i].from], v = Belong[edge[i].to];
		if(u!=v)G[u].push_back(v), du[v]++;
	}
}
void init()
{
    memset(head, -1, sizeof(head));
    edgenum=0;
}

网络流求最小割即最大流

EK

O(n*m2)

#define ll long long
using namespace std;
const ll inf = 34338315071127552;
int n,m,s,t;
struct Node{
    ll v;
    ll val;
    ll next;
}node[20101];
int top=1,head[10101];//top必须从一个奇数开始,一般用-1但我不习惯,解释见下方
void init(int n)
{
    memset(head,-1,sizeof(int)*(n+1));
    top = 1;
}
inline void addedge(ll u,ll v,ll val){
    node[++top].v=v;
    node[top].val=val;
    node[top].next=head[u];
    head[u]=top;
}

inline int Read(){
    int x=0;
    char c=getchar();
    while(c>'9'||c<'0')c=getchar();
    while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
    return x;
}
ll inque[10101];//点是访问过里
struct Pre{
    ll v;//该点的前一个点(从起点过来)
    ll edge;//与该点相连的边(靠近起点的)
}pre[10101];
inline bool bfs(){
    queue<ll>q;
    memset(inque,0,sizeof(inque));
    memset(pre,-1,sizeof(pre));
    inque[s]=1;
    q.push(s);
    while(!q.empty()){
        ll u=q.front();
        q.pop();
        for(int i=head[u];i;i=node[i].next){
            ll d=node[i].v;
            if(!inque[d]&&node[i].val){//node[i].val==0则已经该路径满了
            pre[d].v=u;
            pre[d].edge=i;
            if(d==t)return 1;
            inque[d]=1;
            q.push(d);
            }
        }
    }
    return 0;
}//是否有增广路
ll EK(){
    ll ans=0;
    while(bfs()){
        ll mi=inf;
        for(int i=t;i!=s;i=pre[i].v){
            mi=min(mi,node[pre[i].edge].val);//每次只能增加增广路上最小的边的权值
        }
        for(int i=t;i!=s;i=pre[i].v){
            node[pre[i].edge].val-=mi;
            node[pre[i].edge^1].val+=mi;
            //反向的边的编号是正向边的编号^1
            //这就是为什么top开始时必须是奇数
        }
        ans+=mi;
    }
    return ans;
}

dinic

O(n2*m)

#define ll long long
//Dinic网络最大流模板
const ll inf=1LL*1<<60;
const int maxn = 10101;
int dep[10101],head[10101],inque[10101];
int top=1;
ll maxflow=0;
int n,m,s,t;
struct Node{
    int v;
    ll val;
    int next;
}node[200100];
void init()
{
    memset(head,-1,sizeof(int)*(n+1));
    top = 1;
}
inline void addedge(int u,int v,ll val){
    node[++top].v=v;
    node[top].val=val;
    node[top].next=head[u];
    head[u]=top;
}
 int Read(){
    int x=0,f=1;char c=getchar();
    while(c>'9'||c<'0')c=getchar(),f=-1;
    while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
    return x*f;
}
bool bfs(){
    memset(dep,0x3f,sizeof(int)*(n+1));
    memset(inque,0,sizeof(inque));
    dep[s]=0;
    queue<int>q;
    q.push(s);
    while(!q.empty()){
        int u=q.front();
        q.pop();
        inque[u]=0;
        for(int i=head[u];~i;i=node[i].next){
            int d=node[i].v;
            if(dep[d]>dep[u]+1&&node[i].val){
                dep[d]=dep[u]+1;//注意与EK的区别
                if(inque[d]==0){
                q.push(d);
                inque[d]=1;
                }
            }
        }
    }
    if(dep[t]!=0x3f3f3f3f)return 1;
    return 0;
}//给增广路上的点分层
ll dfs(int u,ll flow){
    ll rlow=0;
    if(u==t){
        maxflow+=flow;//其实可以直接在这里累加最大流
        return flow;
    }
    ll used=0;//该点已经使用的流量
    for(int i=head[u];i;i=node[i].next){
        int d=node[i].v;
        if(node[i].val&&dep[d]==dep[u]+1){
        if(rlow=dfs(d,min(flow-used,node[i].val))){
            used+=rlow;//该点使用的流量增加
            node[i].val-=rlow;
            node[i^1].val+=rlow;
            if(used==flow)break;//该点流量满了,没必要再找了
        }
        }
    }
    return used;//返回该点已使用流量
}//多路寻找增广路
ll Dinic(){
    while(bfs()){
            dfs(s,inf);
    }
    return maxflow;
}//Dinic寻找最大流

tarjan求lca

O(n+q)

#include
#define N 500050
struct hehe{
    int next;
    int to;
    int lca;
};
hehe edge[N];//树的链表
hehe qedge[N];//需要查询LCA的两节点的链表
int n,m,p,x,y;
int num_edge,num_qedge,head[N],qhead[N];
int father[N];
int visit[N];//判断是否被找过
void add_edge(int from,int to){//建立树的链表
    edge[++num_edge].next=head[from];
    edge[num_edge].to=to;
    head[from]=num_edge;
}
void add_qedge(int from,int to){//建立需要查询LCA的两节点的链表
    qedge[++num_qedge].next=qhead[from];
    qedge[num_qedge].to=to;
    qhead[from]=num_qedge;
}
int find(int z){//找爹函数
    if(father[z]!=z)
        father[z]=find(father[z]);
    return father[z];
}
void dfs(int x){//把整棵树的一部分看作以节点x为根节点的小树
    father[x]=x;//由于节点x被看作是根节点,所以把x的father设为它自己
    visit[x]=1;//标记为已被搜索过
    for(int k=head[x];k;k=edge[k].next)//遍历所有与x相连的节点
        if(!visit[edge[k].to]){//若未被搜索
            dfs(edge[k].to);//以该节点为根节点搞小树
            father[edge[k].to]=x;//把x的孩子节点的father重新设为x
        }
    for(int k=qhead[x];k;k=qedge[k].next)//搜索包含节点x的所有询问
        if(visit[qedge[k].to]){//如果另一节点已被搜索过
            qedge[k].lca=find(qedge[k].to);//把另一节点的祖先设为这两个节点的最近公共祖先
            if(k%2)//由于将每一组查询变为两组,所以2n-1和2n的结果是一样的
                qedge[k+1].lca=qedge[k].lca;
            else
                qedge[k-1].lca=qedge[k].lca;
        }
}
int main(){
    scanf("%d%d%d",&n,&m,&p);//输入节点数,查询数和根节点
    for(int i=1;i<n;++i){
        scanf("%d%d",&x,&y);//输入每条边
        add_edge(x,y);
        add_edge(y,x);
    }
    for(int i=1;i<=m;++i){
        scanf("%d%d",&x,&y);//输入每次查询,考虑(u,v)时若查找到u但v未被查找,所以将(u,v)(v,u)全部记录
        add_qedge(x,y);
        add_qedge(y,x);
    }
    dfs(p);//进入以p为根节点的树的深搜
    for(int i=1;i<=m;i++)
        printf("%d\n",qedge[i*2].lca);//两者结果一样,只输出一组即可
    return 0;
}

你可能感兴趣的:(板子)