模板库(三) - 图论算法模板

写在前面

模板库”这一系列文章用来复习 O I OI 模板
由于时间原因,作者无法一一亲自调试其中的程序,也因如此,有一部分程序来自于互联网,如果您觉得这侵犯了您的合法权益,请联系 ( Q Q 2068926345 ) (QQ2068926345) 删除。
对于给您造成的不便和困扰,我表示深深的歉意。
本系列文章仅用于学习,禁止任何人或组织用于商业用途。
本系列文章中,标记*的为选学算法,在 N O I P NOIP 中较少涉及。

图论算法模板

图的遍历和存储

【简介】

图是计算机内部一种常见的数据结构,分为无向图和有向图。
在无向图中,若图中任意一对顶点都是连通的,则称此图是连通图。
在有向图中,若任意一对顶点 u u v v 间存在一条从 u u v v 的路径和从 v v u u 的路径,则称此图是强连通图。
无向图的一个极大连通子图称为该图的一个连通分量。
有向图的一个极大强连通子图称为该图的一个强连通分量。
在图的每条边上加上一个数字作权,也称代价,带权的图称为网。

邻接矩阵存图与遍历

#include<cstdio>
#include<vector>
using namespace std;
#define N 1001
bool vis[N];
int n,m,ans[N],g[N][N];
void dfs(int now,int start){
    vis[now]=true;ans[now]=start;
    for(int i=1;i<=n;i++)
        if(!vis[i]&&g[now][i]) dfs(i,start);
}
int main(){
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        g[y][x]=1;
    }
    for(int i=n;i>=1;i--)if(!vis[i]) dfs(i,i);
    for(int i=1;i<=n;i++)printf("%d%c",ans[i],i==n?'\n':' ');
    return 0; 
}

复杂度 Θ ( n 2 ) Θ(n^2)

Vector存图与遍历

#include<cstdio>
#include<vector>
using namespace std;
#define N 100001
vector<int> g[N];
bool vis[N];
int n,m,ans[N];
void dfs(int now,int start){
    vis[now]=true;ans[now]=start;
    for(int i=0;i^g[now].size();i++)
        if(!vis[g[now][i]]) dfs(g[now][i],start);
}
int main(){
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        g[y].push_back(x);
    }
    for(int i=n;i>=1;i--)if(!vis[i]) dfs(i,i);
    for(int i=1;i<=n;i++)printf("%d%c",ans[i],i==n?'\n':' ');
    return 0; 
}

复杂度 Θ ( n ) Θ(n) ,常数略大

邻接表存图与遍历

#include<cstdio>
struct Edge{
    int next,to;
}edge[200007];
int n,m,head[100007],tot;
bool vis[100007];
inline void add_edge(int from,int to){
    edge[++cnt].next=head[from];
    edge[cnt].to=to;head[from]=cnt;
}
int ans[N];
void dfs(int now,int start){
    vis[now]=true;ans[now]=start;
    for(int i=head[now];i;i=edge[i].nxt)
      if(!vis[edge[i].to]) dfs(edge[i].to,start);
}
int main(){
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        add_edge(y,x);
    }
    for(int i=n;i>=1;i--)if(!vis[i]) dfs(i,i);
    for(int i=1;i<=n;i++)printf("%d%c",ans[i],i==n?'\n':' ');
    return 0;
}

复杂度 Θ ( n ) Θ(n)


一笔画问题

【简介】

如果一个图存在一笔画,则一笔画的路径叫做欧拉路,如果最后又回到起点,那这个路径叫做欧拉回路。
  我们定义奇点是指跟这个点相连的边数目有奇数个的点。对于能够一笔画的图,我们有以下两个定理。
   定理 1 1 :存在欧拉路的条件:图是连通的,有且只有 2 2 个奇点。
   定理 2 2 :存在欧拉回路的条件:图是连通的,有 0 0 个奇点。
  两个定理的正确性是显而易见的,既然每条边都要经过一次,那么对于欧拉路,除了起点和终点外,每个点如果进入了一次,显然一定要出去一次,显然是偶点。对于欧拉回路,每个点进入和出去次数一定都是相等的,显然没有奇点。
  求欧拉路的算法很简单,使用深度优先遍历即可。
  根据一笔画的两个定理,如果寻找欧拉回路,对任意一个点执行深度优先遍历;找欧拉路,则对一个奇点执行 D F S DFS

【代码实现】
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<stack>
using namespace std;
const int maxn = 1031;
int G[maxn][maxn], du[maxn];
int n,m;
stack<int> S;//用一个栈来保存路径
void dfs(int u){
    for(int v=1; v<=n; v++)
        if(G[u][v]){
            G[u][v]--,G[v][u]--;
            dfs(v);    
        }
    S.push(u);
}
int main(){
    cin>>m;
    int u,v;
    for(int i=1; i<=m; i++){
        cin>>u>>v;
        n = max(n,max(u,v));
        G[u][v]++,G[v][u]++;
        du[u]++,du[v]++;
    }//用邻接矩阵记录图
    int s = 1;
    for(int i=1; i<=n; i++)
        if(du[i]%2==1){
            s=i;
            break;
        }//寻找起点
    dfs(s);
    while(!S.empty()){
        cout<<S.top()<<endl;
        S.pop();
    }
    return 0;
}

复杂度 Θ ( n + m ) Θ(n+m)


哈密顿回路问题

【简介】

欧拉回路是指不重复地走过所有路径的回路,而哈密尔顿环是指不重复地走过所有的点,并且最后还能回到起点的回路。

【代码实现】
#include<bits/stdc++.h>
#define max(a,b) (a>b?a:b)
using namespace std;
typedef long long(LL);
typedef unsigned long long(ULL);
const double eps(1e-8);
char B[1<<15],*S=B,*T=B,ch;
#define getc() (S==T&&(T=(S=B)+fread(B,1,1<<15,stdin),S==T)?0:*S++)
int aa,bb; int F(){
      while(ch=getc(),(ch<'0'||ch>'9')&&ch!='-'); ch=='-'?aa=bb=0:(aa=ch-'0',bb=1);
      while(ch=getc(),ch>='0'&&ch<='9')aa=aa*10+ch-'0'; return bb?aa:-aa;
}
#define N 100010
int n,swp,cnt,z[N]; long long ans;
#define swap(a,b) (swp=a,a=b,b=swp)
#define abs(x) (x>0?x:-(x))
#define max(a,b) (a>b?a:b)
#define cmax(x) (ans<x?ans=x:1)
struct P {int x,y,id,nx,ny;} p[N];
bool operator<(const P&a,const P&b) {return a.nx<b.nx||a.nx==b.nx&&a.ny<b.ny;}
class Graph{
private:
      int et,la[N],ufs[N],tot;
      struct D{
            int x,y,v;
            bool operator<(const D&a)const {return v<a.v;}
      } d[N<<2];
      struct E {int to,v,nxt;} e[N<<1];
      int gf(int x) {return ufs[x]==x?x:ufs[x]=gf(ufs[x]);}
      void adde(int x,int y,int v){
            e[++et]=(E) {y,v,la[x]},la[x]=et;
            e[++et]=(E) {x,v,la[y]},la[y]=et;
      }
public:
      Graph() {et=1;}
      void add(int x,int y,int v) {d[++tot]=(D) {x,y,v};}
      void make(){
            std::sort(d+1,d+1+tot);
            for(int i=1; i<=n; i++)ufs[i]=i; cnt=n;
            for(int i=1,x,y; i<=tot; i++)
                  if((x=gf(d[i].x))!=(y=gf(d[i].y))){
                        ufs[x]=y,cnt--,ans+=d[i].v,
                        adde(d[i].x,d[i].y,d[i].v);
                  }
      }
} G;
struct D {int x,n;} d[N];
bool operator<(const D&a,const D&b) {return a.x<b.x;}
#define dis(i,j) (abs(p[i].x-p[j].x)+abs(p[i].y-p[j].y))
void ins(int i){
      for(int t=p[i].ny; t<=cnt; t+=t&-t)
            if(z[t]==0||p[z[t]].x+p[z[t]].y<p[i].x+p[i].y)z[t]=i;
}
int query(int i){
      int f=0;
      for(int t=p[i].ny; t>0; t-=t&-t)
            if(z[t]&&(f==0||p[z[t]].x+p[z[t]].y>p[f].x+p[f].y))f=z[t];
      return f;
}
void work(){
      for(int i=1; i<=n; i++)p[i].nx=p[i].x-p[i].y,p[i].ny=p[i].y;
      std::sort(p+1,p+1+n);
      for(int i=1; i<=n; i++)d[i]=(D) {p[i].ny,i};
      std::sort(d+1,d+1+n); d[n+1].x=d[n].x; cnt=1;
      for(int i=1; i<=n; i++){
            p[d[i].n].ny=cnt;
            if(d[i].x!=d[i+1].x)cnt++;
      }
      memset(z,0,sizeof(z));
      for(int i=1,j; i<=n; ins(i++))
            if(j=query(i))G.add(p[i].id,p[j].id,dis(i,j));
}
int main(){
      n=F();
      for(int i=1; i<=n; i++)p[i]=(P) {F(),F(),i}; work();
      for(int i=1; i<=n; i++)swap(p[i].x,p[i].y); work();
      for(int i=1; i<=n; i++)p[i].y=-p[i].y; work();
      for(int i=1; i<=n; i++)swap(p[i].x,p[i].y); work(); G.make();
      printf("%lld\n",ans);
}

差分约束系统

【简介】

如果一个系统由 n n 个变量和 m m 个约束条件组成,形成 m m 个形如 a i a j k a_i-a_j≤k 的不等式 ( i (i , j [ 1 j∈[1 , n ] n] , k k 为常数 ) ) ,则称其为差分约束系统 ( s y s t e m (system o f of d i f f e r e n c e difference c o n s t r a i n t s ) constraints) 。亦即,差分约束系统是求解关于一组变量的特殊不等式组的方法。

【代码实现】
//P1993 小K的农场
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#include<ctype.h>
#include<iostream>
using namespace std;
typedef pair<int,int> pairs;
priority_queue<pairs,vector<pairs>,greater<pairs> > qq;
int s,cnt,head[10007],dis[10007],vis[10007];
inline int read(){
    int x=0,f=0;char ch=getchar();
    while(!isdigit(ch))f|=ch=='-',ch=getchar();
    while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
    return f?-x:x;
}
struct Edge{
    int next,to,w;
}edge[100007];
inline void add_edge(int from,int to,int w){
    edge[++cnt].next=head[from];edge[cnt].w=w;
    edge[cnt].to=to;head[from]=cnt;
}
inline void dijkstra(){
    memset(dis,0,sizeof dis);
    dis[s]=0;
    qq.push(make_pair(dis[s],s));
    while(!qq.empty()){
        int x=qq.top().second;qq.pop();
        if(vis[x])continue;
        vis[x]=1;
        for(int i=head[x];i;i=edge[i].next){
            int to=edge[i].to;
            if(dis[to]>dis[x]+edge[i].w){
                dis[to]=dis[x]+edge[i].w;
                qq.push(make_pair(dis[to],to));
            }
        }
    }
}
inline bool SPFA(int x){
    vis[x]=1;
    for(int i=head[x];i;i=edge[i].next){
        int to=edge[i].to;
        if(dis[to]<dis[x]+edge[i].w){
            dis[to]=dis[x]+edge[i].w;
            if(vis[to])return 0;
            if(!SPFA(to))return 0;
        }
    }
    vis[x]=0;
    return 1;
}
int main(){
    int n=read(),m=read();
    for(int i=1;i<=m;++i){
        int o=read(),u=read(),v=read();
        if(o==1){
            int w=read();add_edge(v,u,w);
        }
        else if(o==2){
            int w=read();add_edge(u,v,-w);
        }
        else add_edge(u,v,0),add_edge(v,u,0);
    }
    for(int i=1;i<=n;++i)add_edge(0,i,0),dis[i]=-0x3f3f3f3f; 
    cout<<(SPFA(0)?"Yes":"No");
    return 0;
}

生成树算法

Prim最小生成树算法

【简介】

P r i m Prim 算法,图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的树中,不但包括了连通图里的所有顶点,且其所有边的权值之和亦为最小。该算法于 1930 1930 年由捷克数学家沃伊捷赫·亚尔尼克发现;并在 1957 1957 年由美国计算机科学家罗伯特·普里姆独立发现; 1959 1959 年,艾兹格·迪科斯彻再次发现了该算法。

【代码实现】

朴素 P r i m Prim

#include<cstdio>
#include<algorithm>
#define inf 2147483647
#define maxn 5005
#define maxm 200005
struct edge{
    int v,w,next;
}e[maxm<<1];
//注意是无向图,开两倍数组
int head[maxn],dis[maxn],cnt,n,m,tot,now=1,ans;
//已经加入最小生成树的的点到没有加入的点的最短距离
//比如说1和2号节点已经加入了最小生成树,那么dis[3]就等于min(1->3,2->3)
bool vis[maxn];
void add(int u,int v,int w){
    e[++cnt].v=v;e[cnt].w=w;
    e[cnt].next=head[u];head[u]=cnt;
}
int prim(){
    //先把dis数组附为极大值
    for(int i=2;i<=n;++i)dis[i]=inf;
    //这里要注意重边,所以要用到min
    for(int i=head[1];i;i=e[i].next)
        dis[e[i].v]=std::min(dis[e[i].v],e[i].w);
    while(++tot<n)//最小生成树边数等于点数-1
    {
        int minn=inf;//把minn置为极大值
        vis[now]=1;//标记点已经走过
        //枚举每一个没有使用的点
        //找出最小值作为新边
        //注意这里不是枚举now点的所有连边,而是1~n
        for(int i=1;i<=n;++i)if(!vis[i]&&minn>dis[i])minn=dis[i],now=i;
        ans+=minn;
        //枚举now的所有连边,更新dis数组
        for(int i=head[now];i;i=e[i].next){
            int v=e[i].v;
            if(dis[v]>e[i].w&&!vis[v])dis[v]=e[i].w;
        }
    }
    return ans;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1,u,v,w;i<=m;++i){
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w),add(v,u,w);
    }
    printf("%d",prim());
    return 0;
}

复杂度 Θ ( n 2 ) Θ(n^2)


堆优化 P r i m Prim

#include<bits/stdc++.h>
using namespace std;
#define maxn 99999999
struct E{
    int next,c,zhi;
}e[1005000];
inline int read(){
    int x=0,f=0;char ch=getchar();
    while(!isdigit(ch))f|=ch=='-',ch=getchar();
    while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
    return f?-x:x;
}
int last[100500];
int dis[100500];
int n,m,ans,k;
struct node{
    int num;
    node(){}
    node(int h){num=h;}
    bool operator <(const node & p)const{
        return dis[p.num]<dis[num];
    }
};
int located[100500],heap[300500],heap_size;
inline void put(int d){
    int now,next;
    heap[++heap_size]=d;
    now=heap_size;
    located[d]=now;
    while(now>1){
        next=now>>1;
        if(dis[heap[now]]>=dis[heap[next]])break;
        located[d]=next;
        located[heap[next]]=now;
        swap(heap[now],heap[next]);
        now=next;
    }
    return;
}
inline void change(int d){
    int now,next;
    now=located[d];
    while(now>1){
        next=now>>1;
        if(dis[heap[now]]>=dis[heap[next]])break;
        located[d]=next;
        located[heap[next]]=now;
        swap(heap[now],heap[next]);
        now=next;
    }
    return;
}
inline int get(){
    int now,next,res;
    res=heap[1];
    heap[1]=heap[heap_size--];
    now=1;
    located[heap[1]]=1;
    located[res]=0;
    while(now*2<=heap_size){
        next=now*2;
        if(next<heap_size&&dis[heap[next+1]]<dis[heap[next]])++next;
        if(dis[heap[now]]<=dis[heap[next]])break;
        located[heap[now]]=next;
        located[heap[next]]=now;
        swap(heap[next],heap[now]);
        now=next;
    }   
    return res;
}
int main(){
    n=read(),m=read();
    for(int i=1;i<=m;i++){
		int a1,b1,c1;
		a1=read();b1=read();c1=read();
	    e[++k].next=b1;
        e[k].c=c1;
        e[k].zhi=last[a1];
        last[a1]=k;
        e[++k].next=a1;
        e[k].c=c1;
        e[k].zhi=last[b1];
        last[b1]=k;
    }
    for(int i=2;i<=n;i++)dis[i]=maxn;
    for(int j=last[1];j;j=e[j].zhi){
        int y=e[j].next;
        int c=e[j].c;
        if(c<dis[y])dis[y]=c;
    }
    for(int i=2;i<=n;++i)put(i);
    for(int i=1;i<=n-1;++i){
        int x=get();ans+=dis[x];
        for(int j=last[x];j;j=e[j].zhi){
            int y=e[j].next;
            int c=e[j].c;
            if(c<dis[y]){
                dis[y]=c;
                change(y);
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}

复杂度 Θ ( ( n + m ) Θ((n+m) l o g log n n )$


Kruskal最小生成树算法

【简介】

求加权连通图的最小生成树的算法。

【代码实现】
#include<iostream>
#include<cstring>
using namespace std;
int dis[5050],vis[5050],map[5007][5007];
int n,m,a,b,w,ans;
int main(){
    ios::sync_with_stdio(false);
    cin>>n>>m;
    memset(dis,0x3f,sizeof(dis));memset(map,0x3f,sizeof(map));
    for(int i=1;i<=m;++i)
        cin>>a>>b>>w,map[a][b]=map[b][a]=min(map[a][b],w);
    dis[1]=0;
    for(int i=1;i<=n;i++){
        int k=0;
        for(int j=1;j<=n;++j)  if(!vis[j] && dis[j]<dis[k]) k=j;
        vis[k]=1;
        for(int j=1;j<=n;++j)  if(!vis[j]) dis[j]=min(map[k][j],dis[j]);
    }
    for(int i=1;i<=n;i++)
        if(!vis[i]){cout<<"orz";return 0;}
        else ans+=dis[i];
    cout<<ans;
    return 0;
}

复杂度 Θ ( m Θ(m l o g log m ) m)


*严格次小生成树算法

【代码实现】
#include<cstdio>
#include<algorithm>
using namespace std;
#define maxm 300001
#define inf 2147483647
#define maxn 100001
struct Edge {
    int u,v,w,next;
} e[maxm<<1];
struct qj {
    int ma,ma2;
} q[maxn<<2];
struct Edge1 {
    int u,v,w;
    bool operator < (const Edge1 &x)const {
        return w<x.w;//按照边权排序
    }
}edge[maxm];
int n,m,vis[maxm],ans=inf,head[maxn],cnt,fa[maxn],mtree;
void add(int u,int v,int w) {
    e[++cnt].v=v;
    e[cnt].w=w;
    e[cnt].next=head[u];
    head[u]=cnt;
}//前向星加边
namespace smallesttree {
    int find(int x) {
        while(x!=fa[x]) x=fa[x]=fa[fa[x]];
        return x;
    }//并查集找祖先
    void init() {
        for(int i=1; i<=n; i++) fa[i]=i; //预处理并查集
        for(int i=0; i<m; i++)scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
    }
	void kruskal() {
        init();
        sort(edge,edge+m);
        int T=0;
        for(int i=0; i<m; ++i) {
            int eu=find(edge[i].u),ev=find(edge[i].v);//寻找祖先
            if(eu!=ev) {
                add(edge[i].u,edge[i].v,edge[i].w),add(edge[i].v,edge[i].u,edge[i].w);
                mtree+=edge[i].w;//记录子树大小
                fa[ev]=eu;//合并
                vis[i]=1;//标记该边为树边
                if(++T==n-1)break;//边数等于节点数+1即为一颗树
            }
        }
    }
}
//求出最小生成树
namespace treecut {
    int dep[maxn],father[maxn],top[maxn],W[maxn],a[maxn],size[maxn],son[maxn],seg[maxn],col;
    //dep:深度father:父亲节点top:重链的顶端W:到根节点的距离a:点的权值
    //size:子树大小son:重儿子seg:在线段树中的序号(dfs序)
    void dfs1(int u,int fr) {
        dep[u]=dep[fr]+1;
        size[u]=1;
        father[u]=fr;
        for(int i=head[u]; i; i=e[i].next) {
            int v=e[i].v;
            if(v!=fr) {
                W[v]=W[u]+e[i].w;//W为每一个点到根节点的距离
                dfs1(v,u);
                size[u]+=size[v];
                if(size[v]>size[son[u]])son[u]=v;
            }
        }
    }//预处理出dep、size、father以及son
    void dfs2(int now,int fi) {
        top[now]=fi;
        seg[now]=++col;
        a[col]=W[now]-W[father[now]];//a为点的权值(它与之父亲节点边的权值)(相当于前缀和)
        if(!son[now])return;
        dfs2(son[now],fi);
        for(int i=head[now]; i; i=e[i].next) {
            int v=e[i].v;
            if(v!=son[now]&&v!=father[now])dfs2(v,v);
        }
    }//预处理出每个节点的top、seg以及权值
    //树剖模板就不解释了
#define ls k<<1
#define rs k<<1|1
    bool CMP(int a,int b) {
        return a>b;
    }
    int getse(int x,int g,int z,int c) {
        int a[4]= {x,g,z,c};
        sort(a,a+4,CMP);
        for(int i=1; i<3; ++i)
        if(a[i]!=a[0]) return a[i];
        return 0;
    }
    //找到两个区间的最大值和严格次大值(四个数)的最大值与严格次大值
    //就是合并两个区间的最大值和严格次大值
    void build(int k,int l,int r) {
        if(l==r) {
            q[k].ma=a[l];
            return;
        }
        int mid=(l+r)>>1;
        build(ls,l,mid),build(rs,mid+1,r);
        q[k].ma=max(q[ls].ma,q[rs].ma);
        q[k].ma2=getse(q[ls].ma,q[rs].ma,q[ls].ma2,q[rs].ma2);
    }//预处理出区间最大值与次大值
    qj query(int k,int l,int r,int ll,int rr) {
        if(ll>r||rr<l)return(qj){-inf,-inf};
        if(ll<=l&&rr>=r)return(qj){q[k].ma,q[k].ma2};
        int mid=(l+r)>>1;
        qj t1=query(ls,l,mid,ll,rr),t2=query(rs,mid+1,r,ll,rr);
        return(qj){max(t1.ma,t2.ma),getse(t1.ma,t2.ma,t1.ma2,t2.ma2)};
    }//查询区间的区间的最大值与次小值
    int LCA(int u,int v,int d) {
        int need=-inf;
        while(top[u]!=top[v]) {
            if(dep[top[u]]<dep[top[v]])swap(u,v);
            qj temp=query(1,1,n,seg[top[u]],seg[u]);
            u=father[top[u]];
            need=max(need,(temp.ma==d)?temp.ma2:temp.ma);
            //严格次小边(如果temp.ma==k就是非严格次小)
        }
        if(dep[u]<dep[v])swap(u,v);//找到LCA
        qj temp=query(1,1,n,seg[v]+1,seg[u]);
        return max(need,(temp.ma==d)?temp.ma2:temp.ma);//同上
    }
    void init() {
        dfs1(1,0),dfs2(1,1),build(1,1,n);
    }
}
//树链剖分
int main() {
    scanf("%d%d",&n,&m);
    smallesttree::kruskal();//求出最小生成树
    treecut::init();//预处理
    for(int i=0; i<m; ++i) {
        if(vis[i])continue;//枚举所有非树边(没有在最小生成树的边)
        int temp=mtree+edge[i].w-treecut::LCA(edge[i].u,edge[i].v,edge[i].w);
        if(ans>temp&&temp!=mtree+e[i].w&&temp>mtree)ans=temp;
    }
    printf("%d",ans);
    return 0;
}

复杂度 Θ ( m Θ(m l o g 2 n ) log^2n)


生成树计数算法

【代码实现】
#include<iostream>
#include<math.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define zero(x) ((x>0? x:-x)<1e-15)
int const MAXN = 100;
double a[MAXN][MAXN];
double b[MAXN][MAXN];
int g[53][53];
int n,m;
double det(double a[MAXN][MAXN],int n) {
    int i, j, k, sign = 0;
    double ret = 1, t;
    for (i = 0; i < n; i++)
        for (j = 0; j < n; j++)
            b[i][j] = a[i][j];
    for (i = 0; i < n; i++) {
        if (zero(b[i][i])) {
            for (j = i + 1; j < n; j++)
                if (!zero(b[j][i]))break;
            if (j == n)	return 0;
            for (k = i; k < n; k++)
                t = b[i][k], b[i][k] = b[j][k], b[j][k] = t;
            sign++;
        }
        ret *= b[i][i];
        for (k = i + 1; k < n; k++)
            b[i][k] /= b[i][i];
        for (j = i + 1; j < n; j++)
            for (k = i + 1; k < n; k++)
	            b[j][k] -= b[j][i] * b[i][k];
    }
    if (sign & 1)
        ret = -ret;
    return ret;
}
void build() {
    while (m--) {
        int a, b;
        scanf("%d%d", &a, &b);
        g[a-1][b-1]=g[b-1][a-1]=1;
    }
}
int main() {
    int cas;
    scanf("%d", &cas);
    while (cas--) {
        scanf("%d%d", &n, &m);
        memset(g,0,sizeof(g));
        build();
        memset(a,0,sizeof(a));
        for (int i=0; i<n; i++) {
            int d=0;
            for (int j=0; j<n; j++)if(g[i][j])d++;
            a[i][i]=d;
        }
        for (int i=0; i<n; i++)
            for (int j=0; j<n; j++)if(g[i][j])a[i][j]=-1;
        double ans = det(a, n-1);
        printf("%0.0lf\n", ans);
    }
    return 0;
}

复杂度 Θ ( n 3 ) Θ(n^3)


最短路算法

Floyd多源最短路算法

【简介】

F l o y d Floyd 算法又称为插点法,是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与 D i j k s t r a Dijkstra 算法类似。该算法名称以创始人之一、 1978 1978 年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德命名。

【代码实现】
//P2910 [USACO08OPEN]寻宝之路Clear And Present Danger
#include<iostream>
#include<cstring>
using namespace std;
int Must_Through_Island[10007],dis[107][107];
int main()
{
    int n,m,d;
    memset(dis,0x3f,sizeof dis);
    cin>>n>>m;
    for(int i=1;i<=m;++i)cin>>Must_Through_Island[i];
    for(int i=1;i<=n;++i)for(int j=1;j<=n;++j)cin>>d,dis[i][j]=d;
    for(int k=1;k<=n;++k)
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
    int before=1,ans=0;
    for(int i=1;i<=m;++i)ans+=dis[before][Must_Through_Island[i]],before=Must_Through_Island[i];
    cout<<ans;
    return 0;
}

复杂度 Θ ( n 3 ) Θ(n^3)


Dijkstra单源最短路算法

【简介】
迪科斯彻算法使用了广度优先搜索解决赋权有向图或者无向图的单源最短路径问题,算法最终得到一个最短路径树。该算法常用于路由算法或者作为其他图算法的一个子模块。

【代码实现】
朴素Dijkstra
//P2299 Mzc和体委的争夺战
#include<cstdio>
#include<cstring>
#include<complex>
using namespace std;
const int N=2507;
int dis[N],a[N][N];
bool vis[N];
inline int read(){
    int x=0,f=0;char ch=getchar();
    while(!isdigit(ch))f|=ch=='-',ch=getchar();
    while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
    return f?-x:x;
}
int main(){
    memset(a,0x3f,sizeof(a));
    int n=read(),m=read();
    for(int i=1;i<=m;i++){
        int u=read(),v=read(),w=read();
        a[u][v]=a[v][u]=min(w,a[u][v]);
    }
    for(int i=0;i<=n;i++)dis[i]=a[1][i];
    vis[1]=1;dis[1]=0;
    for(int i=1;i<=n;i++){
        int k=0;
        for(int j=1;j<=n;j++)
            if(!vis[j] && dis[j]<dis[k])
                k=j;
        vis[k]=1;
        for(int j=1;j<=n;j++)
            if(dis[j]>dis[k]+a[k][j])
                dis[j]=dis[k]+a[k][j];
    }
    printf("%d\n",dis[n]);
    return 0;
}

复杂度 Θ ( n 2 ) Θ(n^2)

堆优化Dijkstra
#define pairs pair<long long,int>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue> 
using namespace std;
priority_queue<pairs,vector<pairs>,greater<pairs> >q ;
long long dis[100007];
int head[100007];
bool vis[100007];
int n,m,s,t,cnt;
struct Edge{
    int next,to;
    long long w;
}edge[200007];
void add_edge(int from,int to,long long w){
    edge[++cnt].next=head[from];edge[cnt].w=w;
    edge[cnt].to=to;head[from]=cnt;
}
inline void dijkstra(){
    memset(dis,0x3f,sizeof dis);
    dis[s]=0;
    q.push(make_pair(dis[s],s));
    while(!q.empty()){
        int x=q.top().second;q.pop();
        if(vis[x]) continue;
        vis[x]=1;
        for(int i=head[x];i;i=edge[i].next)
            if(dis[x]+edge[i].w<dis[edge[i].to]){
                dis[edge[i].to]=dis[x]+edge[i].w;
                q.push(make_pair(dis[edge[i].to],edge[i].to));
            }
    }
}
int main(){
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=m;++i){
        int u,v;
        long long w;
        scanf("%d%d%lld",&u,&v,&w);
        add_edge(u,v,w);
    }
    dijkstra();
    for(int i=1;i<=n;++i)printf("%lld ",dis[i]);
    return 0;
}

复杂度 Θ ( ( n + m ) Θ((n+m) l o g log n n )$


B e l l m a n F o r d Bellman-Ford 单源最短路算法

【简介】

B e l l m a n F o r d Bellman-Ford 算法是求含负权图的单源最短路径的一种算法,效率较低,代码难度较小。其原理为连续进行松弛,在每次松弛时把每条边都更新一下,若在次松弛后还能更新,则说明图中有负环,因此无法得出结果,否则就完成。

【代码实现】
#include<cstdio>
#include<cstring>
#include<algorithm>
int dis[10010];
int origin[10010],destination[10010],value[10010],S;
int n,m;
int main(){
    scanf("%d%d%d",&n,&m,&S);
    for(int i=1;i<=m;i++)
        scanf("%d%d%d",&origin[i],&destination[i],&value[i]);
    memset(dis,0x7f,sizeof(dis));
    dis[1]=0;
    for(int i=1;i<=n-1;i++)
        for(int j=1;j<=m;j++)
            dis[destination[j]]=std::min(dis[destination[j]],dis[origin[j]]+value[j]);
    for(int i=1;i<=n;i++) printf("%d ",dis[i]);
    return 0;
}

复杂度 Θ ( n m ) Θ(nm)

B e l l m a n F o r d Bellman-Ford 算法的队列优化( S P F A SPFA 单源最短路算法)

【简介】
S P F A SPFA 算法是 B e l l m a n F o r d Bellman-Ford 算法的队列优化算法的别称,通常用于求含负权边的单源最短路径,以及判负权环。

【代码实现】

//P1576 最小花费
#include<iostream>
#include<queue>
#include<cstdio>
using namespace std;
int vis[2020];
double dis[2020],head[2050];
int num_edge;
int n,m,s,x,y,a,b;
double z;
struct Edge{
    int to,from;
    double dis;
}edge[1000050];
void Add(int from,int to,double dis){
    edge[++num_edge].from=head[from];
    edge[num_edge].to=to;
    edge[num_edge].dis=dis;
    head[from]=num_edge;
}
void SPFA(int s){
    queue<int> q;
    q.push(s);
    dis[s]=1;//与自身汇率为1 !!! 
    vis[s]=1;
    while(!q.empty()){
        int u=q.front();q.pop();
        vis[u]=0;
        for(int i=head[u];i;i=edge[i].from){
            int v=edge[i].to;
            if(dis[v]<dis[u]*edge[i].dis){
                dis[v]=dis[u]*edge[i].dis;
                if(vis[v]==0)  q.push(v),vis[v]=1;
            }
        }
    }
}
int main(){
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        cin>>x>>y>>z;
        Add(x,y,double((100-z)/100));
        Add(y,x,double((100-z)/100));
    }
    cin>>a>>b;
    SPFA(a);
    printf("%.8lf",100/double(dis[b]));
    return 0;
}

S P F A SPFA 最坏情况下复杂度和朴素 B e l l m a n F o r d Bellman-Ford 相同,为 Θ ( n m ) Θ(nm)


*Johnson多源最短路算法

【简介】

J o h n s o n Johnson 算法可以求解每对顶点之间的最短路径。对于稀疏图,该算法在要好于 F l o y d Floyd 算法。算法与 F l o y d Floyd 算法类似,每对顶点之间的最短距离用二维数组 D D 表示;如果图中存在负环,算法将输出警告信息。 J o h n s o n Johnson 算法把 B e l l m a n F o r d Bellman-Ford 算法和 D i j k s t r a Dijkstra 算法作为其子函数。

【代码实现】
public class JohnsonAlgo {
    double D[][];
    int P[][];
    GraphLnk G;
    /**
     * 构造函数
     */
    public JohnsonAlgo(GraphLnk G) {
        this.G = G;
        D = new double[G.get_nv()][G.get_nv()];
        P = new int[G.get_nv()][G.get_nv()];
    }
    public boolean Johnson(){
        // 创建一个图_G
        GraphLnk _G = new GraphLnk(G.get_nv() + 1);
        for(int i = 0; i < G.get_nv(); i++){
            for(Edge e = G.firstEdge(i); 
                G.isEdge(e); e = G.nextEdge(e))
                _G.setEdgeWt(e.get_v1(), e.get_v2(), G.getEdgeWt(e));
        }
        // 在原图的基础上添加一个顶点ad
        int ad = _G.get_nv() - 1;
        for(int i = 0; i < G.get_nv(); i++){
            _G.setEdgeWt(ad, i, 0);
        }
         // 首先调用Bellman-Ford算法,以ad为起始点
        MinusWeightGraph swg = new MinusWeightGraph(_G);
        swg.BellmanFord(ad);
        // h函数
        int h[] = new int[G.get_nv() + 1];
        System.out.println("Bellman-Ford算法结果:");
        for(int i = 0; i < _G.get_nv(); i++)
            System.out.print((h[i] = (int)swg.D[i]) + "\t");
        System.out.println();
        for(int i = 0; i < _G.get_nv() - 1; i++)
            for(Edge e = G.firstEdge(i); 
                G.isEdge(e); e = G.nextEdge(e))
                // 检测有没有负环
                if(h[e.get_v2()] > h[e.get_v1()] + _G.getEdgeWt(e))
                {
                    System.out.println("图中有负环。");
                    return false;
                }
                // 如果没有则重赋权
                else{
                    int u = G.edge_v1(e), v = G.edge_v2(e);
                    int wt = (int) (G.getEdgeWt(e) + 
                            h[G.edge_v1(e)] - h[G.edge_v2(e)]);
                    G.setEdgeWt(u, v, wt);
                }
        System.out.println("重赋权后的各条边的权值:");
        for(int u = 0; u < G.get_nv(); u++){
            for(Edge e = G.firstEdge(u); 
                G.isEdge(e);
                e = G.nextEdge(e)){
                System.out.print(u + "-" + e.get_v2() + 
                        " " + G.getEdgeWt(e) + "\t");
            }
            System.out.println();
        }
        // Dijkstra 算法求解每一个顶点的最短路径树
        SingleSourceShortestPaths sssp =  new SingleSourceShortestPaths(G);
        for(int i = 0; i < G.get_nv(); i++){
            sssp.Dijkstra(i);
            System.out.println("\n第" + i + "顶点Dijkstra结果:");
            for(int j = 0; j < G.get_nv(); j++){
                System.out.print(sssp.D[j] + "\t");
                D[i][j] = sssp.D[j] + h[j] - h[i];
                P[i][j] = sssp.V[j];
            }
            System.out.println();
        }
        return true;
    }
}

复杂度 Θ ( n m Θ(nm l o g n ) logn)


Dijkstra次短路算法

【代码实现】
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue> 
using namespace std;
typedef pair<int,int> pairs;
priority_queue<pairs,vector<pairs>,greater<pairs> > q, qq;
int dis[5007],diss[5007],head[5007];
bool vis[5007];
int s=1,cnt;
inline int read(){
    int x=0,f=0;char ch=getchar();
    while(!isdigit(ch))f|=ch=='-',ch=getchar();
    while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
    return f?-x:x;
}
struct Edge{
    int next,to,w;
}edge[200007];
void add_edge(int from,int to,long long w){
    edge[++cnt].next=head[from];edge[cnt].w=w;
    edge[cnt].to=to;head[from]=cnt;
}
inline void dijkstra(int s){
    memset(dis,0x3f,sizeof dis);
    memset(diss,0x3f,sizeof diss);
    dis[s]=0;q.push(make_pair(dis[s],s));
    while(!q.empty()){
        int x=q.top().second;q.pop();
        if(vis[x])continue;
        vis[x]=1;
        for(int i=head[x];i;i=edge[i].next){
            int to=edge[i].to;
            if(dis[to]>dis[x]+edge[i].w){
                dis[to]=dis[x]+edge[i].w;
                q.push(make_pair(dis[to],to));
            }
            else if(dis[to]!=dis[x]+edge[i].w && diss[to]>dis[x]+edge[i].w){
                diss[to]=dis[x]+edge[i].w;
                q.push(make_pair(diss[to],to));
            }
        }
    }
}
int main(){
    int n=read(),m=read();
    for(int i=1;i<=m;++i){
        int u=read(),v=read(),w=read();
        add_edge(u,v,w);add_edge(v,u,w);
    }
    dijkstra(s);
    printf("%d ",diss[n]);
    return 0;
}

复杂度 Θ ( ( n + m ) l o g n ) Θ((n+m)logn)


K短路算法

【代码实现】
#include<bits/stdc++.h>
using namespace std;
const int maxd=5010;
int dis[maxd];
bool vis[maxd];
int n,m,e;
struct sd {
    int num, len;
    bool operator < (const sd &other) const {
        return len > other.len;
    }
};
vector<sd> edge[maxd],redge[maxd];
void spfa()
{
    memset(dis,127,sizeof(dis));
    dis[e]=0;
    queue<int> q;
    q.push(e);
    vis[e]=true;
    while(!q.empty())
    {
        int now=q.front(); q.pop(); vis[now]=false;
        for(int i=redge[now].size()-1;i>=0;--i)
        {
            if(dis[redge[now][i].num]>dis[now]+redge[now][i].len)
            dis[redge[now][i].num]=dis[now]+redge[now][i].len;
            if(!vis[redge[now][i].num])
            {
                vis[redge[now][i].num]=true;
                q.push(redge[now][i].num);
            }
        }
    }
}
int A_star(int cnt)
{
    int ccnt=0;
    priority_queue<sd> q;
    q.push((sd){1,dis[1]});//important
    while(!q.empty())
    {
        sd now=q.top();
        q.pop();
        if(now.num==e)
        {
            ccnt++;
            if(cnt==ccnt)
            {
                return now.len;
            }
            continue;//important
        }
        for(int i=edge[now.num].size()-1;i>=0;--i){
            q.push((sd){edge[now.num][i].num,now.len-dis[now.num]+edge[now.num][i].len+dis[edge[now.num][i].num]});//important
        }
    }
}
int main()
{
    int us;
    scanf("%d%d%d",&n,&m,&e);
    scanf("%d",&us);
    int a,b,c;
    for(int i=1;i<=m;++i)
    {
        scanf("%d%d%d",&a,&b,&c);
        edge[a].push_back((sd){b,c});
        redge[b].push_back((sd){a,c});
    }
    spfa();
    if(us==1) goto flag;
    cout<<A_star(us); return 0;
    flag:
    printf("%d",dis[1]);
    return 0;
}

LCA(最近公共祖先)算法

倍增法求LCA

【简介】

利用二进制的思想,想办法使一步一步向上搜索变成 2 k 2^k 以的向上跳。需要定义一个 f f 数组, f i , j f_{i,j} 使表示节点 i i 2 j 2^j 倍祖先。

【代码实现】

#include<iostream>
#include<ctype.h>
#include<cstdio>
#include<algorithm>
using namespace std;
inline int read(){
    int x=0,f=0;char ch=getchar();
    while(!isdigit(ch))f|=ch=='-',ch=getchar();
    while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
    return f?-x:x;
}
int head[500007],cnt;
int fa[500007][21],deep[500007];
struct Edge{
    int next,to;
}edge[1000007];
inline void add_edge(int from,int to){
    edge[++cnt].next=head[from];
    edge[cnt].to=to;head[from]=cnt;
}
void dfs(int x,int Fa){
    fa[x][0]=Fa;
    deep[x]=deep[Fa]+1;
    for(int i=1;(1<<i)<=deep[x];++i)
        fa[x][i]=fa[fa[x][i-1]][i-1];
    for(int i=head[x];i;i=edge[i].next)
        if(edge[i].to!=Fa)dfs(edge[i].to,x);
}

inline int LCA(int x,int y){
    if(deep[x]>deep[y])swap(x,y);
    for(int i=19;i>=0;--i)
        if(deep[fa[y][i]]>=deep[x])y=fa[y][i];
    if(x==y)return x;
    for(int i=19;i>=0;--i)
        if(fa[x][i]!=fa[y][i])
            x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
int main(){
    int n=read(),m=read(),s=read();
    for(int i=1;i<=n-1;++i){
        int u=read(),v=read();
        add_edge(u,v),add_edge(v,u);
    }
    dfs(s,0);
    for(int i=1;i<=m;++i){
        int x=read(),y=read();
        printf("%d\n",LCA(x,y));
    }
    return 0;
}

复杂度 Θ ( ( n + m ) l o g n ) Θ((n+m)logn)

树链剖分求LCA

【简介】

树链剖分,计算机术语,指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构来维护每一条链。

【代码实现】
#include<cstring>
#include<iostream>
#include<cctype>
#include<cstdio>
#include<algorithm>
#define writ(x,c) write(x),putchar(c);
using namespace std;
inline char nc(){
    static char buf[100000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int read(){
    char c=0;int x=0;bool f=0;
    for(;!isdigit(c);c=nc()) if(c=='-') f=1;
    for(;isdigit(c);c=nc()) x=(x<<1)+(x<<3)+c-48;
    return (f ? -x : x);
}
void write(int x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) write(x/10);
    putchar(x%10+'0');
}
const int N=5e5+1;
struct edge{
    int v,next;
}e[N<<1];
int n,m,root,tot,head[N],dep[N],siz[N],son[N],top[N],f[N];
void add(int u,int v){
    e[++tot].v=v,e[tot].next=head[u],head[u]=tot;
}
void dfs1(int x){
    siz[x]=1,dep[x]=dep[f[x]]+1;
    for(int i=head[x];i;i=e[i].next) {
        if(e[i].v==f[x])continue;
        f[e[i].v]=x;dfs1(e[i].v);
        siz[x]+=siz[e[i].v];
        if(!son[x] || siz[son[x]]<siz[e[i].v])
            son[x]=e[i].v;
    }
}
void dfs2(int x,int tv){
    top[x]=tv;
    if(son[x]) dfs2(son[x],tv);
    for(int i=head[x];i;i=e[i].next)
        if(e[i].v!=f[x] && e[i].v!=son[x])
            dfs2(e[i].v,e[i].v);
}
int main(){
    n=read(),m=read(),root=read();
    register int x,y;
    for(int i=1;i<n;++i){
        x=read(),y=read();
        add(x,y);add(y,x);
    }
    dfs1(root);dfs2(root,root);
    for(int i=1;i<=m;++i){
        x=read(),y=read();
        while(top[x]!=top[y]){
            if(dep[top[x]]>=dep[top[y]])x=f[top[x]];
            else y=f[top[y]];
        }
        writ(dep[x]<dep[y] ? x : y,'\n');
    }
}

复杂度 Θ ( ( n + m ) l o g n ) Θ((n+m)logn)

Tarjan求LCA

【简介】

这种算法是基于 D F S DFS 和并查集来实现的。设 f x f_x x x 的父亲, d i s x dis_x x x 节点到根节点的距离。首先从一号根节点 ( ( 记为 u ) u) 开始访问他的每一个子节点 ( ( 记为 v ) v) ,并用根节点与当前访问的子节点的距离更新 d i s dis 值,即 d i s v = d i s u + m a p v , u dis_v=dis_u+map_{v,u} ,其中 m a p v , u map_{v,u} 表示 v v u u 的距离,然后将当前子节点当做根点用上述同样步骤递归下去,并在递归回溯后将其 f v f_v 值更新,这样的目的是保证子节点 v v 的所有子树全部被访问过。

【代码实现】
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#include<vector>
#include<cctype>
#define add_qedge(x,y) {ql[++qnum]={i,y,qt[x]};qt[x]=qnum;}
#define top 20000000
#define add_edge(x,y) {l[++num]={y,t[x]};t[x]=num;}
#define max_n 500001
using namespace std;
int f[max_n],t[max_n],qt[max_n],lca[max_n];
bool b[max_n];
int i,num,qnum;
char ch[top];int now_r,now_w=-1;
inline void read(int &x)
{
    while (ch[now_r]<48) ++now_r;
    for (x=ch[now_r]-48;ch[++now_r]>=48;)
     x=(x<<1)+(x<<3)+ch[now_r]-48;
}
int q2[max_n],tail2;
void write(int x)
{
    for (;x;x/=10) q2[++tail2]=x%10;
    for (;tail2;--tail2) ch[++now_w]=q2[tail2]+48;
    ch[++now_w]='\n';
}
struct edge{
    int to,next;
}l[max_n<<1];
struct qedge{
    int id,to,next;
}ql[max_n<<1];
struct node {
 int f;
}T[max_n];
void dfs_lca(int &x)
{
    f[x]=x;b[x]=1;
    int y,i;
    for(i=t[x];i;i=l[i].next)
    if((y=l[i].to)!=T[x].f)
    {
        T[y].f=x;
        dfs_lca(y);
        f[y]=x;        
    }
    for(i=qt[x];i;i=ql[i].next)        
    if(b[y=ql[i].to])
    {    
      while (y!=f[y])
      {
          q2[++tail2]=y;y=f[y];
      }
      lca[ql[i].id]=y;   
      while (tail2)
      {
          f[q2[tail2]]=y;
          --tail2;
      } 
    }
}
int main()
{ 
   ch[fread(ch,1,top,stdin)]=0;
    int n,m,root,x,y;
    read(n);read(m);read(root);
    for (i=1;i<n;++i) 
    {
      read(x);read(y);
      add_edge(x,y);add_edge(y,x);
    }
    for (i=1;i<=m;++i) 
    {
      read(x);read(y);
      add_qedge(x,y);add_qedge(y,x);
    }    
    dfs_lca(root);
    for (i=1;i<=m;++i) write(lca[i]);
    fwrite(ch,1,now_w,stdout);
}

复杂度 Θ ( n + m ) Θ(n+m)


强连通分量(缩点)

Kosaraju算法

【简介】

在计算机科学中, K o s a r a j u Kosaraju 的算法 ( ( 也称为 K o s a r a j u S h a r i r Kosaraju-Sharir 算法 ) ) 是线性时间的算法来找到一个有向图的强连通分量。
A h o Aho H o p c r o f t Hopcroft U l l m a n Ullman 相信这个算法是由 S . R a o K o s a r a j u S.RaoKosaraju 1978 1978 在一个未发表的论文上提出的。
相同的算法还从 M i c h a S h a r i r 1981 MichaSharir1981 年自己出版的书上被单独的发现,这个算法利用了一个事实,即转置图 ( ( 同图中的每边的方向相反 ) ) 具有和原图完全一样的强连通分量。

【代码实现】
#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 100001
#define M 1000001
int x[M],y[M],head[M],nxt[M],v[M],w[M],cnt;
int rhead[M],rnxt[M],rv[M],rcnt;
int a[N],c[N],dp[N],mark[N],stack[N],n,m,t,ans;
bool vis[N];
void addline(int x,int y){
    v[cnt]=y,nxt[cnt]=head[x],head[x]=cnt++;
}
void raddline(int x,int y){
    rv[rcnt]=y,rnxt[rcnt]=rhead[x],rhead[x]=rcnt++;
}
void dfs1(int x){
    vis[x]=true;
    for(int i=head[x];i!=-1;i=nxt[i])
        if(!vis[v[i]]) dfs1(v[i]);
    stack[++t]=x;
    return;
}
void dfs2(int x){
    mark[x]=t,c[t]+=a[x];
    for(int i=rhead[x];i!=-1;i=rnxt[i])
        if(!mark[rv[i]]) dfs2(rv[i]);
    return;
}
void dfs3(int x){
    if(dp[x])  return;
    for(int i=head[x];i!=-1;i=nxt[i]) {
        if(!dp[v[i]])  dfs3(v[i]);
        dp[x]=std::max(dp[x],dp[v[i]]);
    }
    dp[x]+=c[x];
    return;
}
int main(){
    memset(head,-1,sizeof(head));
    memset(rhead,-1,sizeof(rhead));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
      scanf("%d",&a[i]);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x[i],&y[i]);
        addline(x[i],y[i]);
        raddline(y[i],x[i]);
    }
    for(int i=1;i<=n;i++)if(!vis[i])  dfs1(i);
    t=0,cnt=0;
    for(int i=n;i>=1;i--)
      if(!mark[stack[i]])  t++,dfs2(stack[i]);
    memset(head,-1,sizeof(head));
    memset(nxt,-1,sizeof(nxt));
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=m;i++)
        if(mark[x[i]]!=mark[y[i]])
            addline(mark[x[i]],mark[y[i]]);
    for(int i=1;i<=t;i++){
        if(!dp[i])  dfs3(i);
        ans=std::max(ans,dp[i]);
    }
    printf("%d\n",ans);
    return 0;
}

复杂度 Θ ( V + E ) Θ(|V|+|E|)

T a r j a n Tarjan 算法

【简介】

T a r j a n Tarjan 算法是用来求有向图的强连通分量的。求有向图的强连通分量的 T a r j a n Tarjan 算法是以其发明者 R o b e r t T a r j a n Robert·Tarjan 命名的。 R o b e r t T a r j a n Robert·Tarjan 还发明了求双连通分量的 T a r j a n Tarjan 算法。

T a r j a n Tarjan 算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。

【代码实现】
#include<iostream>
#include<cstdio>
#include<ctype.h>
#include<cstring>
#include<queue>
using namespace std;
int dfn[10007],low[10007],stack[10007],vis[10007];
int color[10007],sum[10007],d[10007],in[10007];
int head[10007],x[100007],y[100007],w[10007],dis[10007];
int n,m,cnt,color_num,tot,Index;
inline void read(int &x){
    x=0;char ch=getchar();
    while(!isdigit(ch))ch=getchar();
    while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
}
struct Edge{int next,to;}edge[100007];
inline void add_edge(int from,int to){
    edge[++cnt].next=head[from];
    edge[cnt].to=to;head[from]=cnt;
}

void Tarjan(int x){
    dfn[x]=low[x]=++tot;
    stack[++Index]=x,vis[x]=1;
    for(int i=head[x];i;i=edge[i].next){
        int to=edge[i].to;
        if(!dfn[to]){
            Tarjan(to);
            low[x]=min(low[x],low[to]);
        }
        else if(vis[to]){
            low[x]=min(low[x],dfn[to]);
        }
    }
    if(dfn[x]==low[x]){
        ++color_num;
        do{
            color[stack[Index]]=color_num;
            sum[color_num]+=w[stack[Index]];
            vis[stack[Index--]]=0;
        }while(x!=stack[Index+1]);
    }
}
void Topo(){
    queue<int> q;
    for(int i=1;i<=color_num;++i)dis[i]=sum[i];
    for(int i=1;i<=color_num;++i)
        if(!in[i])q.push(i);
    while(!q.empty()){
        int x=q.front();q.pop();
        for(int i=head[x];i;i=edge[i].next){
            int to=edge[i].to;
            in[to]--;
            dis[to]=max(dis[to],dis[x]+sum[to]);
            if(!in[to]){
                q.push(to);
            }
        }
    }
    int ans=0;
    for(int i=1;i<=color_num;++i)ans=max(ans,dis[i]);
    printf("%d",ans);
}
int main(){
    read(n),read(m);
    for(int i=1;i<=n;++i)read(w[i]);
    for(int i=1;i<=m;++i){
        read(x[i]),read(y[i]);
        add_edge(x[i],y[i]);
    }
    for(int i=1;i<=n;++i)
        if(!dfn[i])Tarjan(i);
    memset(head,0,sizeof head),cnt=0;
    for(int i=1;i<=m;++i){
        if(color[x[i]]!=color[y[i]]){
            add_edge(color[x[i]],color[y[i]]);
            ++in[color[y[i]]];
        }
    }
    Topo();
    return 0;
}

复杂度 Θ ( V + E ) Θ(|V|+|E|)


二分图算法

匈牙利二分图最大匹配算法

【代码实现】(邻接矩阵)
#include<iostream>
#include<cstring>
#define A 1010
using namespace std;
int f[A][A],g[A],vis[A];
int n,m,e,ans;
int find(int x){
    for(int i=1;i<=m;i++)
        if(f[x][i] and !vis[i]){
            vis[i]=1;
            if(!g[i] or find(g[i])){
                g[i]=x;
                return true;
        }
    }
    return false;
}
int main(){
    cin>>n>>m>>e;
    for(int i=1;i<=e;i++){
        int x,y;
        cin>>x>>y;
        f[x][y]=1;
    }
    for(int i=1;i<=n;i++)
        if(find(i))memset(vis,0,sizeof vis),ans++;
    cout<<ans;
    return 0;
}

复杂度 Θ ( n 2 m ) Θ(n^2m)

【代码实现】(邻接表)
#include<iostream>
#include<cstdio>
#include<ctype.h>
using namespace std;
inline int read(){
    int x=0,f=0;char ch=getchar();
    while(!isdigit(ch))f|=ch=='-',ch=getchar();
    while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
    return f?-x:x;
}
int head[100007],cnt,sum;
struct Edge{int next,to;}edge[200007];
inline void add_edge(int from,int to){
    edge[++cnt].next=head[from];
    edge[cnt].to=to;head[from]=cnt;
}
int n,m,tot,q,gril[1001],head[1001],ans;
bool vis[1001];
bool find(int now){
    for(int i=head[now];i;i=e[i].nxt){
        int to=edge[i].to;
        if(!vis[to]){
            vis[to]=1;
            if(!gril[to] || dfs(gril[to])){
                gril[to]=now;return true;
            }
        }
    }
    return false;
}
int main(){
    int n=read(),m=read(),q=read();
    for(int i=1;i<=q;i++){
        int x=read(),y=read();
        add_edge(x,y);
    } 
    for(int i=1;i<=n;i++){
        memset(vis,0,sizeof vis);
        ans+=dfs(i);
    }
    printf("%d\n",ans);
    return 0;
}

复杂度 Θ ( n m ) Θ(nm)

Dinic实现二分图最大匹配

【代码实现】
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=1e6,inf=1e9;
inline int read(){
    int x=0,f=0;char ch=getchar();
    while(!isdigit(ch))f|=ch=='-',ch=getchar();
    while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
    return f?-x:x;
}
inline void write(int x){
     if(x>9) write(x/10);
     putchar(x%10+'0');
}
struct edge{
    int next,to,c,f;
}e[maxn];
int head[maxn],cur[maxn],lev[maxn],s,t,n,m,tot,ed;
void add(int u,int v,int c){
    e[tot].next=head[u],e[tot].c=c,e[tot].f=0,e[tot].to=v,head[u]=tot++;
    e[tot].next=head[v],e[tot].c=0,e[tot].f=0,e[tot].to=u,head[v]=tot++;
}
bool bfs(){
    memset(lev,0,sizeof(lev));
    queue<int> q;
    q.push(s);
    lev[s]=1;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        for(int i=head[u];i!=-1;i=e[i].next){
            int v=e[i].to;
            if(!lev[v]&&e[i].c>e[i].f){
                q.push(v);
                lev[v]=lev[u]+1;
            }
        }
    }
    return lev[t]!=0;
}
int dfs(int u,int cpf){
    if(u==t) return cpf;
    int adf=0;
    for(int i=(cur[u]!=0?cur[u]:head[u]);i!=-1&&adf<cpf;i=e[i].next){
        int v=e[i].to;
        if(lev[v]==lev[u]+1&&e[i].c>e[i].f){
            int tmp=dfs(v,min(e[i].c-e[i].f,cpf-adf));
            e[i].f+=tmp,e[i^1].f-=tmp;
            adf+=tmp;
            cur[u]=i;
        }
    }
    return adf;
}
int dinic(){
    int maxflow=0;
    while(bfs()){
        memset(cur,0,sizeof(cur));
        maxflow+=dfs(s,inf);
    }
    return maxflow;
}
int main(){
    n=read(),m=read(),ed=read(),s=n+m+3,t=n+m+4;
    memset(head,-1,sizeof(head));
    for(int i=1;i<=ed;i++){
        int u=read(),v=read();
        add(u,v+n,1);
    }
    for(int i=1;i<=n;i++) add(s,i,1);
    for(int i=n+1;i<=m+n;i++) add(i,t,1);
    write(dinic()),putchar('\n');
}

复杂度:由于流量为 1 1 D i n i c Dinic 算法在二分图上的复杂度为 Θ ( n m ) Θ(n\sqrt m)

*KM最大权匹配算法

【代码实现】
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
const int maxn=407,inf=1e9;
int nl,nr,m;
struct KuhnMunkres{
    int n;//左边0~n-1个点,右边0~n-1个点
    int a[maxn+5][maxn+5];
    int lx[maxn+5],ly[maxn+5],sla[maxn+5];//hl是左边顶标  hr是右边顶标
    int fl[maxn+5],fr[maxn+5];//fl[i]表示左边第i个点匹配右边哪个点 fr[i]表示右边第i个点匹配哪个点
    int vx[maxn+5],vy[maxn+5],pre[maxn+5];
    int q[maxn+5],tp;
    void match(int x){
        while(x){
            fr[x]=pre[x];
            int y=fl[fr[x]];
            fl[fr[x]]=x;
            x=y;
        }
    }
    void find(int x){
        fill(vx,vx+n+1,0);
        fill(vy,vy+n+1,0);
        fill(sla,sla+n+1,inf);
        q[tp=1]=x;vx[x]=1;
        while(1){
            for(int i=1;i<=tp;i++){
                int x=q[i];
                for(int y=1;y<=n;y++){
                    int t=lx[x]+ly[y]-a[x][y];
                    if(vy[y]||t>sla[y])continue;
                    pre[y]=x;
                    if(!t){
                        if(!fr[y]){match(y);return;}
                        q[++tp]=fr[y];vy[y]=1;vx[fr[y]]=1;
                    }
                    else sla[y]=t;
            }
        }
        int d=inf;tp=0;
        for(int i=1;i<=n;i++)if(!vy[i]&&d>sla[i])d=sla[i],x=i;
        for(int i=1;i<=n;i++){
            if(vx[i])lx[i]-=d;
            if(vy[i])ly[i]+=d;
            else sla[i]-=d;
        }
        if(!fr[x]){match(x);return;}
        q[++tp]=fr[x];vy[x]=vx[fr[x]]=1;
    }
}
    void solve(){
        memset(lx,0,sizeof(lx));
        memset(ly,0,sizeof(ly));
        memset(fl,0,sizeof(fl));
        memset(fr,0,sizeof(fr));
        for(int i=1;i<=n;++i) lx[i]=*max_element(a[i]+1,a[i]+n+1);
        for(int i=1;i<=n;++i) find(i);
    }
}km;
int main(){
    scanf("%d%d%d",&nl,&nr,&m);
    while(m--){
        int u,v;
        scanf("%d%d",&u,&v);
        scanf("%d",&km.a[u][v]);
    }
    km.n=max(nl,nr);
    km.solve();
    long long ans=0;
    for(int i=1;i<=nl;++i)ans+=km.a[i][km.fl[i]];
    printf("%lld\n",ans);
    for(int i=1;i<=nl;++i)
        if(km.a[i][km.fl[i]]==0) printf("0 ");
        else printf("%d ",km.fl[i]);
    return 0;
}

复杂度 Θ ( n 3 ) Θ(n^3)

*网络流算法

Edmonds-Karp最大流算法

【简介】

E K EK 算法基于一个基本的方法 : F o r d F u l k e r s o n :Ford-Fulkerson 方法 即增广路方法 简称$FF¥方法。

增广路方法是很多网络流算法的基础一般都在残留网络中实现其思路是每次找出一条从源到汇的能够增加流的路径 调整流值和残留网络 不断调整直到没有增广路为止

【代码实现】
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int INF=0x7ffffff;
queue <int> q;
int n,m,x,y,s,t,g[201][201],pre[201],flow[201],maxflow; 
//g邻接矩阵存图,pre增广路径中每个点的前驱,flow源点到这个点的流量 
inline int bfs(int s,int t){
    while (!q.empty()) q.pop();
    for (int i=1; i<=n; i++) pre[i]=-1;
    pre[s]=0;
    q.push(s);
    flow[s]=INF;
    while (!q.empty()){
        int x=q.front();
        q.pop();
        if (x==t) break;
        for (int i=1; i<=n; i++)
          //EK一次只找一个增广路 
          if (g[x][i]>0 && pre[i]==-1){
            pre[i]=x;
            flow[i]=min(flow[x],g[x][i]);
            q.push(i);
          }
    }
    if (pre[t]==-1) return -1;
    else return flow[t];
}
//increase为增广的流量 
void EK(int s,int t){
    int increase=0;
    while ((increase=bfs(s,t))!=-1){//迭代 
        int k=t;
        while (k!=s){
            int last=pre[k];//从后往前找路径
            g[last][k]-=increase;
            g[k][last]+=increase;
            k=last;
        }
        maxflow+=increase;
    }
}
int main(){
    scanf("%d%d",&m,&n);
    for (int i=1; i<=m; i++){
        int z;
        scanf("%d%d%d",&x,&y,&z);
        g[x][y]+=z;
    }
    EK(1,n);
    printf("%d",maxflow);
    return 0;
}

复杂度 Θ ( n m 2 ) Θ(nm^2)

D i n i c Dinic 最大流算法

【简介】

D i n i c Dinic 算法在 E K EK 算法的基础上增加了分层图的概念,根据从 s s 到各个点的最短距离的不同,把整个图分层。寻找的增广路要求满足所有的点分别属于不同的层,且若增广路为 s , P 1 , P 2 P k , t s,P_1,P_2…P_k,t ,点在分层图中的所属的层记为 D e e p v Deep_v ,那么应满足 D e e p p i = D e e p p i 1 + 1 Deep_{p_i}=Deep_{p_{i−1}}+1

【代码实现】
#include<iostream>
#include<cstdio>
#include<queue>
#include<cctype>
#include<cstring>
using namespace std;
const int inf=1e9;
inline int read(){
    int x=0,f=0;char ch=getchar();
    while(!isdigit(ch))f|=ch=='-',ch=getchar();
    while(isdigit(ch))x=x*10+(ch^48),ch=getchar();
    return f?-x:x;
}
int n,m,s,t,cnt=1,ans;
int head[200007],cur[200007],deep[200007];
struct Edge{
    int next,to,w;
}edge[200007];
void add_edge(int from,int to,int w,bool flag){
    edge[++cnt].next=head[from];
    edge[cnt].to=to;head[from]=cnt;
    if(flag)edge[cnt].w=w;
}
inline bool bfs(int s,int t){
    memset(deep,0x3f,sizeof deep);
    queue<int> q;
    for(int i=1;i<=n;++i)cur[i]=head[i];
    deep[s]=0;q.push(s);
    while(!q.empty()){
        int x=q.front();q.pop();
        for(int i=head[x];i!=-1;i=edge[i].next){
            int to=edge[i].to;
            if(deep[to]>inf && edge[i].w){
                deep[to]=deep[x]+1;q.push(to);
            }
        }
    }
    return deep[t]<inf;
}
int dfs(int x,int t,int limit){
    if(!limit || x==t)return limit;
    int flow=0,f;
    for(int i=cur[x];i!=-1;i=edge[i].next){
        cur[x]=i;
        int to=edge[i].to;
        if(deep[to]==deep[x]+1 && (f=dfs(to,t,min(limit,edge[i].w)))){
            flow+=f,limit-=f;edge[i].w-=f,edge[i^1].w+=f;
            if(!limit)break;
        }
    }
    return flow;
}
inline void Dinic(int s,int t){
    while(bfs(s,t))ans+=dfs(s,t,inf);
    printf("%d",ans);
}
int main(){
    n=read(),m=read(),s=read(),t=read();
    memset(head,-1,sizeof head);
    for(int i=1;i<=m;++i){
        int u=read(),v=read(),w=read();
        add_edge(u,v,w,1);add_edge(v,u,w,0);
    }
    Dinic(s,t);
    return 0;
}

复杂度 Θ ( n 2 m ) Θ(n^2m)

*ISAP最大流算法

【简介】

I S A P ( I m p r o v e d ISAP(Improved S h o r t e s t Shortest A u g m e n t i n g Augmenting P a t h ) Path) 也是基于分层思想的最大流算法。所不同的是,它省去了 D i n i c Dinic 每次增广后需要重新构建分层图的麻烦,而是在每次增广完成后自动更新每个点的『标号』(也就是所在的层)

最短增广路算法是一种运用距离标号使寻找增广路的时间复杂度下降的算法。所谓的距离标号就是某个点到汇点的最少的弧的数量 ( ( 即当边权为 1 1 时某个点的最短路径长度 ) ) . 设点 i i 的标号为 d i d_i , 那么如果将满足 d i = d j + 1 d_i=d_j+1 , 且增广时只走允许弧, 那么就可以达到"怎么走都是最短路"的效果. 每个点的初始标号可以在一开始用一次从汇点沿所有反向的 B F S BFS 求出.

【代码实现】
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int inf=1e9;
queue <int> q;
int m,n,x,y,z,maxflow,head[5000],num_edge=-1;
int cur[5000],deep[5000],last[5000],num[5000];
//cur当前弧优化; last该点的上一条边; num桶 用来GAP优化 
struct Edge{
    int next,to,dis;
}edge[500];
void add_edge(int from,int to,int dis,bool flag){
    edge[++num_edge].next=head[from];
    edge[num_edge].to=to;
    edge[num_edge].dis=dis;
    head[from]=num_edge;
}
//bfs仅用于更新deep 
void bfs(int t){
    while (!q.empty()) q.pop();
    for (int i=0; i<=m; i++) cur[i]=head[i];
    for (int i=1; i<=n; i++) deep[i]=n;
    deep[t]=0;
    q.push(t);
    while (!q.empty()) {
        int now=q.front(); q.pop();
        for (int i=head[now]; i!=-1; i=edge[i].next){
            if (deep[edge[i].to]==n && edge[i^1].dis){//i^1是为了找反边 
                deep[edge[i].to]=deep[now]+1;
                q.push(edge[i].to);
            }
        }
    }
}
int add_flow(int s,int t){
    int ans=inf,now=t;
    while (now!=s){
        ans=min(ans,edge[last[now]].dis);
        now=edge[last[now]^1].to;
    }
    now=t;
    while (now!=s){
        edge[last[now]].dis-=ans;
        edge[last[now]^1].dis+=ans;
        now=edge[last[now]^1].to;
    }
    return ans;
}
void isap(int s,int t){
    int now=s;
    bfs(t);//搜出一条增广路
    for (int i=1; i<=n; i++) num[deep[i]]++;
    while (deep[s]<n){
        if (now==t){//如果到达汇点就直接增广,重新回到源点进行下一轮增广 
            maxflow+=add_flow(s,t);
            now=s;//回到源点 
        }
        bool has_find=0;
        for (int i=cur[now]; i!=-1; i=edge[i].next){
            if (deep[now]==deep[edge[i].to]+1 && edge[i].dis){//找到一条增广路 
                has_find=true;
                cur[now]=i;//当前弧优化
                now=edge[i].to;
                last[edge[i].to]=i;
                break;
            }
        }
        if (!has_find){//没有找到出边,重新编号 
            int minn=n-1;
            for (int i=head[now]; i!=-1; i=edge[i].next)//回头找路径 
                if (edge[i].dis)
                    minn=min(minn,deep[edge[i].to]);
            if ((--num[deep[now]])==0) break;//GAP优化 出现了断层 
            num[deep[now]=minn+1]++;
            cur[now]=head[now];
            if (now!=s)now=edge[last[now]^1].to;
        }
    }
}
int main(){
    memset(head,-1,sizeof(head));
    scanf("%d%d",&m,&n);
    for (int i=1; i<=m; i++){
        scanf("%d%d%d",&x,&y,&z);
        add_edge(x,y,z,1); add_edge(y,x,z,0); 
    }
    isap(1,n);
    printf("%d",maxflow);
    return 0;
}

复杂度 Θ ( n 2 m ) Θ(n^2m) ,但在非二分图上表现得比 D i n i c Dinic 要好。

*HLPP最高标号预流推进算法

【代码实现】
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int MAXN=2*1e3+10;
const int INF=1e8+10;
inline char nc(){
    static char buf[MAXN],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXN,stdin),p1==p2)?EOF:*p1++;
}
inline int read(){
    char c=nc();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=nc();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=nc();}
    return x*f;
}
int N,M,S,T;
int H[MAXN];//每个节点的高度
int F[MAXN];//每个节点可以流出的流量
int gap[MAXN];//每个高度的数量 
struct node{
    int u,v,flow,nxt;
}edge[MAXN];
int head[MAXN];
int num=0;//注意这里num必须从0开始 
inline void add_edge(int x,int y,int z){
    edge[num].u=x;
    edge[num].v=y;
    edge[num].flow=z;
    edge[num].nxt=head[x];
    head[x]=num++;
}
inline void AddEdge(int x,int y,int z){
    add_edge(x,y,z);
    add_edge(y,x,0);//注意这里别忘了加反向边 
}
struct comp{
    int pos,h;
    comp(int pos=0,int h=0):pos(pos),h(h) {}
    inline bool operator < (const comp &a) const {return h<a.h;} 
};
priority_queue<comp>q;
bool Work(int u,int v,int id){
    int val=min(F[u],edge[id].flow);
    edge[id].flow-=val;edge[id^1].flow+=val;
    F[u]-=val;F[v]+=val;
    return val;
}
inline int HLPP(){
    H[S]=N;F[S]=INF;q.push(comp(S,H[S]));
    while(q.size()!=0){
        int p=q.top().pos;q.pop();
        if(!F[p]) continue;
        for(int i=head[p];i!=-1;i=edge[i].nxt)
            if( (p==S||H[edge[i].v]+1==H[p]) && Work(p,edge[i].v,i) && edge[i].v!=S && edge[i].v!=T)
                q.push( comp(edge[i].v,H[edge[i].v]) );
        if(p!=S && p!=T && F[p]){
            if( (--gap[ H[p] ])==0 ){//该高度不存在 
                for(int i=1;i<=N;i++)
                    if( H[p]<H[i]&&H[i]<=N && p!=S && p!=T ) 
                        H[i]=N+1;//设置为不可访问 
            }
            ++gap[ ++H[p] ];//高度+1 
            q.push( comp(p,H[p]) );
        }
    }
    return F[T];
}
int main(){
    memset(head,-1,sizeof(head));
    N=read(),M=read(),S=read(),T=read();
    for(int i=1;i<=M;i++){
        int x=read(),y=read(),z=read();
        AddEdge(x,y,z); 
    }
    printf("%d", HLPP() ); 
    return 0;
}

复杂度 Θ ( n 2 m ) Θ(n^2\sqrt m)

*zkw最小费用最大流

【代码实现】
#include <iostream>
#include <algorithm>
#include <map>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define MAXN 50050
#define MAXN_ 5050
#define INF 0x3f3f3f3f
using namespace std;
struct edge{ int to,cap,cost,rev;};
int n,m,flow,s,t,cap,res,cost,from,to;
std::vector<edge> G[MAXN_];
int dist[MAXN_],prevv[MAXN_],preve[MAXN_]; // 最短路的前驱节点 和 对应的边
inline void add(){
  //也许你看到这里会看不懂,为什么要存一个 size 进来, 那么别急,我们下边的存反边 会用到的!
    G[from].push_back((edge){to,cap,cost,(int)G[to].size()});
    G[to].push_back((edge){from,0,-cost,(int)G[from].size()-1});
}
inline int read(){
    int x=0;
    char c=getchar();
    bool flag=0;
    while(c<'0'||c>'9'){if(c=='-')flag=1;    c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?-x:x;
}
inline void min_cost_flow(int s,int t,int f){
    while(f > 0){
        memset(dist,INF,sizeof dist);
        dist[s] = 0;
        bool update = true;
        while(update){
            update = false;
            for(int i=1;i<=n;i++){
                if(dist[i] == INF) continue;
                for(int j=0;j<(int)G[i].size(); ++j){
                    edge &e = G[i][j];
                    if(e.cap > 0 && dist[e.to] > dist[i] + e.cost){
                        dist[e.to] = dist[i] + e.cost;
                        prevv[e.to] = i;
                        preve[e.to] = j;
                        update = true; 
                    } 
                }
            }
        }
        // 此时的 INF 表示再也没有 增广路径,于是就是最后的答案了!
        if(dist[t] == INF) break;
        int d = f;
        for(int v = t; v != s; v = prevv[v])d = min(d,G[prevv[v]][preve[v]].cap);
        // d 就是 增广路的 流量!
        f -= cap;
        flow += d;
        res += d * dist[t];
        for(int v=t;v!=s;v=prevv[v]){
            edge &e = G[prevv[v]][preve[v]];
            e.cap -= d;
            G[v][e.rev].cap += d; // 给 反边加上适当的边权!
        }
    }
}
int main(int argc,char const* argv[]){
   n = read(); m = read(); s = read(); t = read();
   for(int i=1;i<=m;++i){
           scanf("%d%d%d%d",&from,&to,&cap,&cost);
           add();
   }
   min_cost_flow(s,t,INF);
   printf("%d %d\n",flow,res);
   return 0;
}

*Primal-Dual原始对偶算法

【代码实现】
#include <iostream>
#include <algorithm>
#include <queue>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define MAXN_ 5050
#define INF 0x3f3f3f3f
#define P pair<int,int>
using namespace std;
struct edge{ int to,cap,cost,rev;};
int n,m,flow,s,t,cap,res,cost,from,to,h[MAXN_];
std::vector<edge> G[MAXN_];
int dist[MAXN_],prevv[MAXN_],preve[MAXN_]; // 前驱节点和对应边
inline void add(){
    G[from].push_back((edge){to,cap,cost,(int)G[to].size()});
    G[to].push_back((edge){from,0,-cost,(int)G[from].size()-1});
} // 在vector 之中找到边的位置所在!
inline int read(){
    int x=0;
    char c=getchar();
    bool flag=0;
    while(c<'0'||c>'9'){if(c=='-')flag=1;    c=getchar();}
    while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?-x:x;
}
inline void min_cost_flow(int s,int t,int f){
    fill(h+1,h+1+n,0);
    while(f > 0){
        priority_queue<P,vector<P>, greater<P> > D;
        memset(dist,INF,sizeof dist);
        dist[s] = 0; D.push(P(0,s));
        while(!D.empty()){
            P now = D.top(); D.pop();
            if(dist[now.second] < now.first) continue;
            int v = now.second;
            for(int i=0;i<(int)G[v].size();++i){
                edge &e = G[v][i];
                if(e.cap > 0 && dist[e.to] > dist[v] + e.cost + h[v] - h[e.to]){
                    dist[e.to] = dist[v] + e.cost + h[v] - h[e.to];
                    prevv[e.to] = v;
                    preve[e.to] = i;
                    D.push(P(dist[e.to],e.to));
                }
            }
        }
        // 无法增广 , 就是找到了答案了!
        if(dist[t] == INF) break;
        for(int i=1;i<=n;++i) h[i] += dist[i]; 
        int d = f;
        for(int v = t; v != s; v = prevv[v])
            d = min(d,G[prevv[v]][preve[v]].cap);
        f -= d; flow += d;
        res += d * h[t];
        for(int v=t;v!=s;v=prevv[v]){
            edge &e = G[prevv[v]][preve[v]];
            e.cap -= d;
            G[v][e.rev].cap += d;
        }
    }
}
int main(int argc,char const* argv[]){
   n = read(); m = read(); s = read(); t = read();
   for(int i=1;i<=m;++i){
           from = read(); to = read(); cap = read(); cost = read();
           add();
   }
   min_cost_flow(s,t,INF);
   printf("%d %d\n",flow,res);
   return 0;
}

*上下界最大流

【代码实现】
#include<iostream>
#include<string>
#include<algorithm>
#include<cstdlib>
#include<cstdio>
#include<set>
#include<map>
#include<vector>
#include<cstring>
#include<stack>
#include<cmath>
#include<queue>
using namespace std;
#define CL(x,v); memset(x,v,sizeof(x));
#define INF 0x3f3f3f3f
#define LL long long
#define REP(i,r,n) for(int i=r;i<=n;i++)
#define RREP(i,n,r) for(int i=n;i>=r;i--)
const int MAXN=1500;
struct Edge{
    int from,to,cap,flow;
};
bool cmp(const Edge& a,const Edge& b){
    return a.from < b.from || (a.from == b.from && a.to < b.to);
}
struct Dinic{
    int n,m,s,t;
    vector<Edge> edges;
    vector<int> G[MAXN];
    bool vis[MAXN];
    int d[MAXN];
    int cur[MAXN];
    void init(int n){
        this->n=n;
        for(int i=0;i<=n;i++)G[i].clear();
        edges.clear();
    }
    void AddEdge(int from,int to,int cap){
        edges.push_back((Edge){from,to,cap,0});
        edges.push_back((Edge){to,from,0,0});//当是无向图时,反向边容量也是cap,有向边时,反向边容量是0
        m=edges.size();
        G[from].push_back(m-2);
        G[to].push_back(m-1);
    }
    bool BFS(){
        CL(vis,0);
        queue<int> Q;
        Q.push(s);
        d[s]=0;
        vis[s]=1;
        while(!Q.empty()){
            int x=Q.front();
            Q.pop();
            for(int i=0;i<G[x].size();i++){
                Edge& e=edges[G[x][i]];
                if(!vis[e.to]&&e.cap>e.flow){
                    vis[e.to]=1;
                    d[e.to]=d[x]+1;
                    Q.push(e.to);
                }
            }
        }
        return vis[t];
    }
    int DFS(int x,int a){
        if(x==t||a==0)return a;
        int flow=0,f;
        for(int& i=cur[x];i<G[x].size();i++){
            Edge& e=edges[G[x][i]];
            if(d[x]+1==d[e.to]&&(f=DFS(e.to,min(a,e.cap-e.flow)))>0){
                e.flow+=f;
                edges[G[x][i]^1].flow-=f;
                flow+=f;
                a-=f;
                if(a==0)break;
            }
        }
        return flow;
    }
    //当所求流量大于need时就退出,降低时间
    int Maxflow(int s,int t,int need){
        this->s=s;this->t=t;
        int flow=0;
        while(BFS()){
            CL(cur,0);
            flow+=DFS(s,INF);
            if(flow>need)return flow;
        }
        return flow;
    }
    //最小割割边
    vector<int> Mincut(){
        BFS();
        vector<int> ans;
        for(int i=0;i<edges.size();i++){
            Edge& e=edges[i];
            if(vis[e.from]&&!vis[e.to]&&e.cap>0)ans.push_back(i);
        }
        return ans;
    }
    void Reduce(){
        for(int i = 0; i < edges.size(); i++) edges[i].cap -= edges[i].flow;
    }
    void ClearFlow(){
        for(int i = 0; i < edges.size(); i++) edges[i].flow = 0;
    }
};
int n,m;
Dinic solver;
int du[MAXN];
int dn[MAXN][MAXN];
int id[MAXN][MAXN];
int main(){
    while(~scanf("%d%d",&n,&m)){
        int s=0,t=n+m+1;
        int ss=n+m+2,tt=n+m+3;
        int a,b,c;
        CL(du,0);
        CL(dn,0);
        CL(id,0);
        solver.init(n+m+5);
        REP(i,1,m){
            scanf("%d",&a);
            solver.AddEdge(n+i,t,INF-a);
            du[n+i]-=a;
            du[t]+=a;
        }
        int C,D;
        REP(i,1,n)
        {
            scanf("%d%d",&C,&D);
            solver.AddEdge(s,i,D);
            REP(j,1,C)
            {
                scanf("%d%d%d",&a,&b,&c);
                solver.AddEdge(i,a+n+1,c-b);
                du[i]-=b;
                du[a+n+1]+=b;
                dn[i][a]=b;
                id[i][a]=solver.edges.size()-2;
            }
        }
        solver.AddEdge(t,s,INF);
        int sum=0;
        REP(i,1,t){
            if(du[i]<0){
                solver.AddEdge(i,tt,-du[i]);
            }
            else if(du[i]>0){
                solver.AddEdge(ss,i,du[i]);
                sum+=du[i];
            }
        }
        int maxflow=solver.Maxflow(ss,tt,INF);
        if(maxflow==sum){
            int ans=solver.Maxflow(s,t,INF);
            printf("%d\n",ans);
            for(int i=1;i<=n;i++){
                for(int j=0;j<m;j++)
                    if(id[i][j])
                        printf("%d\n",solver.edges[id[i][j]].flow+dn[i][j]);
            }
        }
        else printf("-1\n");
        printf("\n");
    }
    return 0;
} 

猜你喜欢

转载自blog.csdn.net/weixin_44023181/article/details/84945733
今日推荐