定理整理:

关于二分图:

(1) 二分图的最小路径覆盖

1.最小不相交路径覆盖 :Res==节点数-最大匹配数

2.最小可相交路径覆盖:首先floyd算法跑出所有可以到达的点,之后Res==节点数-最大匹配数 

(2)二分图的最小顶点覆盖:

定义:若选择一个点说明选择与它相连的所有边,最小顶点覆盖就是选择最少的点来覆盖所有的边。

最小定点覆盖==二分图最大匹配

(3)二分图最大独立集

定义:选出一些顶点使得这些顶点两两不相邻,则这些点构成的集合称为独立集。最大独立集为包含顶点数最多的独立集。

定理:最大独立集 = 所有顶点数 - 最小顶点覆盖 =最小路径覆盖

(4)二分图的最大团

定义: 团:选出一些点,使其两两之间都有边。 最大团:点数最大的团

定理:二分图的最大团 = 补图的最大独立集

感性理解:最大独立集为两两之间没有边,那么补图的最大独立集说明在原图中两两之间有边,那么就是原图的最大团

(5)极大匹配(Maximal Matching):是指在当前已完成的匹配下,无法再通过增加未完成匹配的边的方式来增加匹配的边数。

(6)最大匹配(maximum matching):是所有极大匹配当中边数最大的一个匹配,设为M。选择这样的边数最大的子集称为图的最大匹配问题。

(7)完美匹配(完备匹配):一个图中所有的顶点都是匹配点的匹配,即2|M| = |V|。完美匹配一定是最大匹配,但并非每个图都存在完美匹配。

(8)最优匹配:最优匹配又称为带权最大匹配,是指在带有权值边的二分图中,求一个匹配使得匹配边上的权值和最大。一般X和Y集合顶点个数相同,最优匹配也是一个完备匹配,即每个顶点都被匹配。如果个数不相等,可以通过补点加0边实现转化。一般使用KM算法解决该问题。(KM(Kuhn and Munkres)算法,是对匈牙利算法的一种贪心扩展。)

关于欧拉图:

(1)无向图存在欧拉回路的充要条件

一个无向图存在欧拉回路,当且仅当该图所有顶点度数都为偶数,且该图是连通图。

因为要遍历完所有的边,所以每一个点而言,只要有进去的边,就一定要有出去的边

(2)有向图存在欧拉回路的充要条件

一个有向图存在欧拉回路,所有顶点的入度等于出度且该图是连通图。

同无向图

关于其他一般图

(1)拓扑排序:

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,边<u,v>∈E(G),则u在线性序列中出现在v之前。

Others Continue...

模板整理:

1.二分图匈牙利匹配:

bool used[maxn];
int girl[maxn];
int boy[maxn];
bool judge(int x)
{
    int sz=v[x].size();
    for(int k=0;k<sz;k++)
    {
        ll e=v[x][k];
        if(!used[e])
        {
            used[e]=true;
            if(!girl[e]||judge(girl[e]))
            {
                girl[e]=x;
                boy[x]=e;
                return true;
            }
        }
    }
    return false;
}
//MaxMatch()
for(int i=1;i<=n;i++)
 {
      for(int k=1;k<=m;k++) used[k]=false;
      if(judge(i)&&boy[i]==-1) p++;//boy数组自己开
 }

2.二分图染色

bool dfs(int u,int c)
{
    vis[u]=c;
    int sz=v[u].size();
    for(int i=0;i<sz;i++)
    {
        int e=v[u][i];
        if(vis[e]==c) return false;
        if(!vis[e]&&!dfs(e,-c)) return false;
    }
    return true;
}
//判断是否为二分图
for(int i=1;i<=n;i++)
    if(vis[i]==0&&!judge(i)) //不是二分图
//否则则是并已成功染色

3.Hopcroft-Karp匹配算法 O(sqrt(n)*m) 【带优化】

/***
主函数加 memeset()
***/
ll n,m,p;
int dR[maxn],dL[maxn];//增广路距离
int nR[maxn],nL[maxn];//左右标记
bool used[maxn];//使用标记
ll cnt=0,dis;
vector<int>v[maxn];//邻接表
bool judge()//寻找最短增广路
{
    queue<int>q;
    memset(dR,-1,sizeof(dR));
    memset(dL,-1,sizeof(dL));
    for(int i=1;i<=cnt;i++)//cnt 换成 y集有多少个点
    {
        if(nL[i]==-1){
            q.push(i);
            dL[i]=0;
        }
    }
    dis=INF;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        if(dL[u]>dis) break;
        int sz=v[u].size();
        for(int i=0;i<sz;i++)
        {
            int e=v[u][i];
            if(dR[e]==-1)//保证增广路不相交
            {
                dR[e]=dL[u]+1;
                if(nR[e]==-1) dis=dR[e];
                else{
                    dL[nR[e]]=dR[e]+1;
                    q.push(nR[e]);
                }
            }
        }
    }
    return dis!=INF;
}
bool Find(int x)
{
    int sz=v[x].size();
    for(int i=0;i<sz;i++)
    {
        int e=v[x][i];
        if(dR[e]==dL[x]+1&&!used[e])
        {
            used[e]=true;
            if(nR[e]!=-1&&dis==dR[e]) continue;//优化
            if(nR[e]==-1||Find(nR[e]))
            {
                nR[e]=x;
                nL[x]=e;
                return true;
            }
        }
    }
    return false;
}
ll MaxMatch()
{
    ll res=0;
    while(judge())
    {
        memset(used,false,sizeof(used));
        for(int i=1;i<=n;i++)
            if(nL[i]==-1&&Find(i)) res++;
    }
    return res;
}

4.KM算法,带权图最佳匹配

/***
不是完全图的边补成完全图 补0
slack的本质优化:
slack表示的是 右边到左边最小的差值,也就是说 上一次匹配过程中 如果要形成增广路最少减少多少
***/
const int INF = 0x3f3f3f3f;
int mp[1005][1005];// 完全图 无边补0
int nL[1005],nR[1005];//左边与右边匹配的点
int xL[1005],xR[1005];//可行性顶标
bool L[1005], R[1005];//标记是否在一轮匹配中被匹配过
int slack[1005];//优化松弛数组

ll n,m;
bool hungry(int x)
{
    L[x]=true;
    for(int i=1;i<=n;i++)
    {
        int gap=xL[x]+xR[i]-mp[x][i];
        if(gap==0)
        {
            if(!R[i])
            {
                R[i]=true;
                if(nR[i]==-1||hungry(nR[i]))
                {
                    nR[i]=x;
                    nL[x]=i;
                    return true;
                }
            }
        }
        else
            slack[i]=min(slack[i],gap);
    }
    return false;
}
ll km()
{
    memset(xR,0,sizeof(xR));
    memset(xL,0,sizeof(xL));
    for(int i=1;i<=n;i++)
        for(int k=1;k<=n;k++)
            xL[i]=max(xL[i],mp[i][k]);
    for(int i=1;i<=n;i++)
    {
        for(int k=1;k<=n;k++) slack[k]=INF;
        while(1)
        {
            memset(L,false,sizeof(L));
            memset(R,false,sizeof(R));
            if(hungry(i)) break;
            int d=INF;
            for(int k=1;k<=n;k++)
                if(!R[k]) d=min(slack[k],d);
            for(int k=1;k<=n;k++)
            {
                if(L[k]) xL[k]-=d;
                if(R[k]) xR[k]+=d;
                else  slack[k]-=d;//因为没有被访问过,但是左边的预期值
            }
        }
    }
    ll res=0;
    for(int i=1;i<=n;i++)
        res+=mp[nR[i]][i];
    return res;
}

5.km算法求最小权值匹配  (带权图 最小费用圈覆盖 )

/****
与上面模板相同 mp数组初始化为-INF
其余不变
最后结果取反即可
****/

6.匈牙利算法 多重匹配:n^3~n^4 可解决最小化分组问题

vector<int>v[1500];
bool used[1500];
vector<int>nR[1500];
bool dfs(int x)
{
    int sz=v[x].size();
    for(int i=0;i<sz;i++)
    {
        int e=v[x][i];
        if(!used[e])
        {
            used[e]=true;
            int sz=nR[e].size();
            if(sz<limit)
            {
                nR[e].push_back(x);
                return true;
            }
            else
            {
                for(int k=0;k<sz;k++)
                {
                    if(dfs(nR[e][k]))
                    {
                        nR[e][k]=x;
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

6.djistra+堆优化的最短路(N+vlgv)

struct N{
    int id;
    ll w;
    bool friend operator<(N a,N b)
    {
        return a.w>b.w;
    }
};
void dijstra()
{
    for(int i=1;i<=n;i++) dis[i]=INF;
    dis[1]=0;
    N s;s.id=1;s.w=0;
    priority_queue<N>q;
    q.push(s);
    while(1)
    {
        if(q.empty()) break;
        N u=q.top();q.pop();
        if(dis[u.id]!=u.w) continue;
        for(int i=head[u.id];~i;i=edge[i].next)
        {
            int e=edge[i].e;
            if(dis[e]>u.w+edge[i].w)
            {
                dis[e]=u.w+edge[i].w;
                N now;now.id=e;
                now.w=dis[e];
                q.push(now);
            }
        }
    }
    return;
}

6.最短路径还原

void AC(int u)
{
    if(path[u]!=-1) AC(path[u]);
    printf("%d ",u);
}

7.A*第k短路  注释为:严格第k短路

ll n,m,p;//n个点,m条边,第p短路
int s,t;//起点和终点
ll cnt=0,cnt1=0;
int head[maxn],head1[maxn];//正向建图,反向建图
ll dis[5005];
bool vis[5005];
int shirt[5005];
struct edg{
    int e,next;
    ll w;
}edge[maxn],edge1[maxn];
struct node
{
    int id;ll g;
    friend bool operator<(node a,node b)
    {
        return a.g+dis[a.id]>b.g+dis[b.id];
    }
};
void restart()
{
    memset(head,-1,sizeof(head));
    memset(head1,-1,sizeof(head1));
    cnt=cnt1=0;
}
void addedge(int u,int v,double w)
{
    edge[cnt]=edg{v,head[u],w};
    head[u]=cnt++;
    edge1[cnt1]=edg{u,head1[v],w};
    head1[v]=cnt1++;
}
void spfa()//反向跑最短路
{
    for(int i=1;i<=n;i++) dis[i]=INF,vis[i]=false;
    queue<int>q;
    q.push(t);dis[t]=0;vis[t]=true;
    while(!q.empty())
    {
        int u=q.front();q.pop();vis[u]=false;
        for(int i=head1[u];~i;i=edge1[i].next)
        {
            int e=edge1[i].e;
            if(dis[e]>dis[u]+edge1[i].w)
            {
                dis[e]=dis[u]+edge1[i].w;
                if(!vis[e])
                {
                    vis[e]=true;
                    q.push(e);
                }
            }
        }
    }
}
ll A_start()
{
    ll cot=0;
    if(s==t) p++;//如果s==t 因为起点为s 防止cot++;
    //ll pre=-1;//严格第k短路
    priority_queue<node>q;
    q.push(node{s,0});
    if(dis[s]==INF) return -1;
    while(!q.empty())
    {
        node u=q.top();q.pop();
        if(u.id==t) 
        {
          //  if(u.g!=pre) {cot++;pre=u.g;}//严格第k短路
            cot++;
        }
        if(cot==p) return u.g;
        shirt[u.id]++;
        if(shirt[u.id]>p) continue;
        for(int i=head[u.id];~i;i=edge[i].next)
            q.push(node{edge[i].e,u.g+edge[i].w});
    }
    return -1;
}

8.严格/非严格第二短路

void spfa()
{
    for(int i=1;i<=n;i++) dis[i]=dis1[i]=INF,vis[i]=false;
    queue<N>q;dis[1]=0;
    vis[1]=true;N s;s.w=0;s.id=1;q.push(s);
    while(!q.empty())
    {
        N u=q.front();q.pop();
        for(int i=head[u.id];~i;i=edge[i].next)
        {
            int e=edge[i].e;
            if(dis[e]>u.w+edge[i].w)
            {
                dis1[e]=dis[e];
                dis[e]=u.w+edge[i].w;
                q.push(N{e,dis[e]});
            }
            else if(dis1[e]>u.w+edge[i].w&&u.w+edge[i].w>dis[e])//非严格去除条件即可
            {
                dis1[e]=u.w+edge[i].w;
                q.push(N{e,dis1[e]});
            }
        }
    }
}

9.spfa判负环

int judge()
{
    for(int i=0;i<=n;i++) dis[i]=INF,vis[i]=false;
    for(int i=0;i<=n;i++) cot[i]=0;
    queue<int>q;q.push(1);vis[1]=true;
    dis[1]=0;
    while(!q.empty())
    {
        int u=q.front();q.pop();vis[u]=false;
        if(cot[u]>n+1) return 1;
        for(int i=head[u];~i;i=edge[i].next)
        {
            int e=edge[i].e;
            if(dis[e]>dis[u]+edge[i].w)
            {
                dis[e]=dis[u]+edge[i].w;
                cot[e]++;
                if(!vis[e])
                {
                    q.push(e);
                    vis[e]=true;
                }
            }
        }
    }
    return 0;
}

10.判断补图的连通性(点的个数 1e6可) 静态链表优化

int head[maxn];
struct node{
    int e,next;
}edge[maxn];
int pre[maxn],cur[maxn];
int vis[maxn];//标记每个点是否被访问
int d[maxn];//标记BFS中每个点是否被访问
int res[maxn];//每个联通块有多少个点
ll cnt=0;
void addedge(int u,int v)
{
    edge[cnt]=node{v,head[u]};
    head[u]=cnt++;
    edge[cnt]=node{u,head[v]};
    head[v]=cnt++;
}
int main()
{
    int cot=0;
    memset(head,-1,sizeof(head));
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        addedge(x,y);
    }
    int s=1;
    for(int i=1;i<=n;i++){
        pre[i]=i-1;
        cur[i]=i+1;
    }
    cur[n]=-1;pre[1]=-1;
    pre[1]=-1;cur[n]=-1;
    for(int i=1;i<=n;i++)if(!vis[i]){
        queue<int>q;q.push(i);
        vis[i]=++cot;
        res[cot]++;
        while(!q.empty())
        {
            int u=q.front();q.pop();
            for(int k=head[u];~k;k=edge[k].next)
            {
                int e=edge[k].e;
                if(!d[e]) d[e]=1;
            }
            for(int k=s;~k;k=cur[k])
            {
                if(!d[k])
                {
                    if(!vis[k])
                    {
                        cur[pre[k]]=cur[k];
                        pre[cur[k]]=pre[k];
                        if(k==s) s++;
                        vis[k]=cot;
                        res[cot]++;
                        q.push(k);
                    }
                }
                else
                    d[k]=0;
            }
        }
    }

11.拓扑排序 复杂度O (n+m)

ll cot=0;
void Topological_Order()
{
    queue<int>q;
    for(int i=1;i<=n;i++)
        if(!in[i]) q.push(i);
    cot=0;
    while(!q.empty())
    {
        int u=q.front();q.pop();
        res[++cot]=u;
        for(int i=head[u];~i;i=edge[i].next)
        {
            int e=edge[i].e;
            in[e]--;
            if(in[e]==0) q.push(e);
        }
    }
    if(cot==n){
        for(int i=1;i<=cot;i++) printf("%d ",res[i]);
        printf("\n");
    }
    else  puts("NO\n");
}

12.有向图判断连通性 (并查集)

int Find(int x)
{
    return pre[x]==x?x:pre[x]=Find(pre[x]);
}
void Merge(int x,int y)
{
    int dx=Find(x);
    int dy=Find(y);
    if(dx!=dy)
        pre[dx]=dy;
    return;
}
int x=Find(1);
for(int i=1;i<=n;i++)
    if(Find(i)!=x) //不连通

13.强连通分量 缩点 Tarjan O(n+m)

int dfn[maxn],low[maxn];//dfs序号 他能连接到的最小根节点
int st[maxn],s=0;//栈
int sct[maxn],szsc[maxn],scc=0;//强连通分量所属,强连通分量个数
int inst[maxn];//标记是否在栈中
int cot=0;//dfs序
void Tarjan(int u)
{
    st[++s]=u;inst[u]=1;
    low[u]=dfn[u]=++cot;
    for(int i=head[u];~i;i=edge[i].next)
    {
        int e=edge[i].e;
        if(!dfn[e]){
            Tarjan(e);
            low[u]=min(low[u],low[e]);
        }
        else if(inst[e]) low[u]=min(low[u],dfn[e]);
        //else 被访问过并且不在栈里 那肯定已经为强连通分量了
    }
    if(low[u]==dfn[u]){
        scc++;//强连通分量++
        while(1)
        {
            int x=st[s--];
            inst[x]=0;
            sct[x]=scc;
            szsc[scc]++;//强连通分量的大小为1
            if(u==x) break;
        }
    }
}

13.最大度生成树 优先队列每次选择度最大的

struct Degree{
    int w;
    int id;
    bool friend operator<(Degree a,Degree b)
    {
        return a.w>b.w;
    }
};
void bfs()
{
    Degree s;s.w=-1;
    priority_queue<Degree>q;
    for(int i=1;i<=n;i++)
        if(in[i]>s.w)
        {
            s.w=in[i];
            s.id=i;
        }
    q.push(s);vis[s.id]=1;
    while(!q.empty())
    {
        Degree u=q.top();q.pop();
        for(int i=head[u.id];~i;i=edge[i].next){
            int e=edge[i].e;
            if(!vis[e])
            {
                printf("%d %d\n",u.id,e);
                in[e]--;
                vis[e]=1;
                q.push({in[e],e});
            }
        }
    }
}

14.LCA树上两点最近公共祖先(倍增)

int deep[maxn];//树的深度
int f[maxn][30];//fa[i][j] 节点i的2^j次方的祖先
void dfs(int u,int fa)//预处理深度 与祖先的关系
{
    deep[u]=deep[fa]+1;
    f[u][0]=fa;
    for(int i=1;(1<<i)<=deep[u];i++)//不加限制也行
        f[u][i]=f[f[u][i-1]][i-1];//2^i = 2^(i-1)+2^(i-1)
    for(int i=head[u];~i;i=edge[i].next){
        int e=edge[i].e;
        if(e!=fa) dfs(e,u);
    }
}
int LCA(int u,int v)
{
    if(deep[u]<deep[v]) swap(u,v);//保持u比v深
    for(int i=25;i>=0;i--)   if(deep[f[u][i]]>=deep[v]) u=f[u][i];//比他深往上跳
    if(u==v) return u;
    for(int i=25;i>=0;i--){
        if(f[u][i]!=f[v][i]){//因为从同一深度开始向上跳 一样有可能是更远的祖先
            u=f[u][i];
            v=f[v][i];
        }
    }
    return f[u][0];//随便返回一个上一级即可
}
/***
主函数调动
dfs(p,p)  // p为根节点  选择不同的根可能会造成不同的结果
***/

15.树上两点最近公共祖先  Tarjan

int head[maxn],qhead[maxn];//保存原图 与 询问图
struct node{
    int e,next;
    int lca;
}edge[maxn],qedge[maxn];//保存原图 与 询问图
int fa[maxn];//与并查集类似,回溯时更新
int vis[maxn];//标记点是否访问过
ll cnt=0,cnt1=0;
/****
存图过程省略
****/
int Find(int x)//找爹函数  类似并查集
{
    return x==fa[x]?x:fa[x]=Find(fa[x]);
}
void dfs(int u)
{
    fa[u]=u;//起初每一个节点的父亲都设为自己
    vis[u]=1;//已被搜索过
    for(int i=head[u];~i;i=edge[i].next)
    {
        int e=edge[i].e;
        if(!vis[e]){
            dfs(e);
            fa[e]=u;
        }
    }
    for(int i=qhead[u];~i;i=qedge[i].next)//回溯时处理
    {
        int e=qedge[i].e;
        if(vis[e]){
            if(i%2) qedge[i].lca=qedge[i-1].lca=Find(e);
            else qedge[i].lca=qedge[i+1].lca=Find(e);
        }
    }
}

16.Tarjan算法求割点

ll cnt=0,ans=0;
int res[maxn];//储存割点
int dfn[maxn],low[maxn],cot=0;//当前DFS顺序,可到最短顺序标号
map<int,int>mp;//判重  n很小数组就可以 map省空间
void Tarjan_min(int u,int fa)
{
    dfn[u]=low[u]=++cot;
    int child=0;
    for(int i=head[u];~i;i=edge[i].next)
    {
        int e=edge[i].e;
        if(!dfn[e])
        {
            child++;
            Tarjan_min(e,u);
            low[u]=min(low[u],low[e]);
            if(fa==-1&&child>=2&&!mp[u]){
                    res[++ans]=u;
                    mp[u]=1;
            }
            if(fa!=-1&&low[e]>=dfn[u]&&!mp[u]){
                    res[++ans]=u;
                    mp[u]=1;
                }
        }
        else if(e!=fa)//父边
            low[u]=min(low[u],dfn[e]);//必须为dfn[e]  例:1-2 2-3 3-4 4-3 3-1 3为割点
    }
}
/***主函数中调用   考虑到图不连通的情况
for(int i=1;i<=n;i++)
    if(!dfn[i])
        Tarjan_min(i,-1); 
***/

17.Floyd最小环及路径还原:

int mat[N][N], dist[N][N];
int next[N][N]; // next[i][j]表示i到j经历的第一个点。
int path[N];
int cnt, n;

void Floyd()
{
    int mins=INF;
    for(int k=1; k<=n; k++)
    {
        for(int i=1; i<k; i++)
        for(int j=i+1; j<k; j++)
        {
            int tmp = dist[i][j]+mat[i][k]+mat[k][j];
            if(tmp < mins)// 更新最小环的权值
            {
                mins = tmp;
                cnt=0;
                int p = i;
                while(p!=j) // 记录最小环的路径
                {
                    path[cnt++] = p;
                    p = next[p][j];
                }
                path[cnt++] = j;
                path[cnt++] = k;
            }
        }
        for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)
        {
            if(dist[i][k]+dist[k][j] < dist[i][j])
            {
                dist[i][j] = dist[i][k] + dist[k][j];
                next[i][j] = next[i][k];
            }
        }
    }
    if(mins==INF)
        puts("No solution.");
    else
    {
        for(int i=0; i<cnt; i++)
            printf("%d%s", path[i], i==cnt-1 ? "\n":" ");
    }
}

18.欧拉回路及其还原

/***
存在欧拉回路需要判断:
1.图必须联通
2.无向图:所有点的度数均为偶数
  有向图:所有点的入度等于出度
***/
int head[maxn];
int path[maxn],ans=0;//边路径
int N[maxn],cot=0;//点路径
int vis[maxn];//标记每条边被访问
ll cnt=0;
struct node{
    int e,next,id;//为每条边进行标号
}edge[maxn];
void addedge(int u,int v,int id)
{
    edge[cnt]=node{v,head[u],id};
    head[u]=cnt++;
    edge[cnt]=node{u,head[v],id};
    head[v]=cnt++;
}
void Search(int u)
{
    for(int i=head[u];~i;i=edge[i].next)
    {
        if(!vis[i]){
            vis[i]=1;
            Search(edge[i].e);
            path[++ans]=i;//要么连着起点,要么连着与起点相连的边
            N[++cot]=u;
        }
    }
}
/***
主函数:
    N[++cot]=s;//s为起点
    Search(s);
***/

Others Continue...