图论

最短路:

//权值非负,不连通为INF、
//O(n^2)
void dij(int beg)
{
    memset(dis,0x3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[beg] = 0;
    for(int i = 1;i <= n;i++)
    {
        int k = -1,minn = INF;
        for(int j = 1;j <= n;j++)
        {
            if(!vis[j] && dis[j] < minn)
            {
                minn = dis[j];
                k = j;
            }
        }
        if(k == -1) break;
        vis[k] = 1;
        for(int j = 1;j <= n;j++)
        {
            if(!vis[j] && dis[k]+a[k][j] < dis[j])
            {
                dis[j] = dis[k]+a[k][j];
            }
        }
    }
}
dij邻接矩阵
///O(mlogm)
struct xxx
{
    int to,w;
    xxx(int a,int b):to(a),w(b){};
    friend bool operator <(xxx X,xxx Y)
    {
        return X.w > Y.w;
    }
};

void dij(int beg)
{
    priority_queue<xxx> q;
    memset(dis,0x3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    dis[beg] = 0;
    q.push(xxx(beg,0));
    while(!q.empty())
    {
        int now = q.top().to;
        q.pop();
        if(vis[now])    continue;
        vis[now] = 1;
        for(int i = 0;i < v[now].size();i++)
        {
            int t = v[now][i].to,w = v[now][i].w;
            if(!vis[t] && dis[now]+w < dis[t])
            {
                dis[t] = dis[now]+w;
                q.push(xxx(t,dis[t]));
            }
        }
    }
}
dij优先队列
//O(n^3)
void floyd()
{
    for(int k = 0;k < n;k++)
    {
        for(int i = 0;i < n;i++)
        {
            for(int j = 0;j < n;j++)    a[i][j] = min(a[i][j],a[i][k]+a[k][j]);
        }
    }
}
floyd
//v储存所有边
//O(nm)
struct xxx
{
    int u,v,w;
    xxx(int a,int b,int c):u(a),v(b),w(c){};
};
vector<xxx> v;

//存在负环返回0,否则返回1
bool bellman_ford(int beg)
{
    memset(dis,0x3f,sizeof(dis));
    dis[beg] = 0;
    for(int i = 1;i < n;i++)
    {
        for(int j = 0;j < v.size();j++)
        {
            int x = v[j].u,y = v[j].v,w = v[j].w;
            dis[y] = min(dis[y],dis[x]+w);
        }
    }
    for(int i = 0;i < v.size();i++)
    {
        int x = v[i].u,y = v[i].v,w = v[i].w;
        if(dis[y] > dis[x]+w)   return 0;
    }
    return 1;
}
bellman_ford
//O(km)
struct xxx
{
    int to,w;
    xxx(int a,int b):to(a),w(b){};
};
vector<xxx> v[205];

//存在负环返回0,否则返回1
bool spfa(int beg)
{
    queue<int> q;
    memset(c,0,sizeof(c));
    memset(vis,0,sizeof(vis));
    memset(dis,0x3f,sizeof(dis));
    dis[beg] = 0;
    q.push(beg);
    vis[beg] = 1;
    c[beg] = 1;
    while(!q.empty())
    {
        int now = q.front();
        q.pop();
        vis[now] = 0;
        for(int i = 0;i < v[now].size();i++)
        {
            int t = v[now][i].to,w = v[now][i].w;
            if(dis[now]+w < dis[t])
            {
                dis[t] = dis[now]+w;
                vis[t] = 1;
                q.push(t);
                if(++c[t] > n)   return 0;
            }
        }
    }
    return 1;
}
spfa
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;

struct xxx
{
    int to,w;
    xxx(int a,int b):to(a),w(b){};
    friend bool operator<(xxx x,xxx y)
    {
        return x.w > y.w;
    }
};
vector<xxx> v[11005];
int n,m,k,a[1005][1005],dis[11005],vis[11005];

void dij(int beg)
{
    memset(dis,0x3f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    priority_queue<xxx> q;
    dis[beg] = 0;
    q.push(xxx(beg,0));
    while(!q.empty())
    {
        int now = q.top().to;
        q.pop();
        if(vis[now])    continue;
        vis[now] = 1;
        for(int i = 0;i < v[now].size();i++)
        {
            int t = v[now][i].to,w = v[now][i].w;
            if(w+dis[now] < dis[t])
            {
                dis[t] = w+dis[now];
                q.push(xxx(t,dis[t]));
            }
        }

    }
}

int main()
{
    while(~scanf("%d%d%d",&n,&m,&k))
    {
        for(int i = 1;i <= 11000;i++)    v[i].clear();
        memset(a,0x3f,sizeof(a));
        for(int i = 1;i <= m;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            a[u][v] = min(a[u][v],w);
            a[v][u] = min(a[v][u],w);
        }
        for(int i = 1;i <= n;i++)
        {
            for(int j = 1;j <= n;j++)
            {
                if(i == j || a[i][j] == INF)    continue;
                for(int l = 0;l <= k;l++)
                {
                    v[l*n+i].push_back(xxx(l*n+j,a[i][j]));
                    if(l != k)
                    {
                        v[l*n+i].push_back(xxx((l+1)*n+j,0));
                    }
                }
            }
        }
        dij(1);
        printf("%d
",dis[(k+1)*n]);
    }
    return 0;
}
分层图最短路

最小生成树:

//不连通返回-1,否则返回最小花费
int prim()
{
    int ans = 0;
    memset(vis,0,sizeof(vis));
    vis[1] = 1;
    for(int i = 2;i <= n;i++)   cost[i] = a[1][i];
    for(int i = 2;i <= n;i++)
    {
        int minn = INF;
        int k = -1;
        for(int j = 1;j <= n;j++)
        {
            if(!vis[j] && minn > cost[j])
            {
                minn = cost[j];
                k = j;
            }
        }
        if(minn == INF) return -1;
        ans += minn;
        vis[k] = 1;
        for(int j = 1;j <= n;j++)
        {
            if(!vis[j] && cost[j] > a[k][j])    cost[j] = a[k][j];
        }
    }
    return ans;
}
prim邻接矩阵
struct xxx
{
    int to,w;
    xxx(int a,int b):to(a),w(b){}
    friend bool operator<(xxx a,xxx b)
    {
        return a.w > b.w;
    }
};
//非连通返回-1,否则返回最小花费
int prim()
{
    memset(vis,0,sizeof(vis));
    for(int i = 0;i < n;i++)    dis[i] = INT_MAX;
    int ans = 0;
    priority_queue<xxx> q;
    q.push(xxx(1,0));
    while(!q.empty())
    {
        while(!q.empty() && vis[q.top().to])    q.pop();
        if(q.empty())   break;
        ans += q.top().w;
        int now = q.top().to;
        q.pop();
        vis[now] = 1;
        for(int i = 0;i < v[now].size();i++)
        {
            int t = v[now][i].to,w = v[now][i].w;
            if(vis[t])  continue;
            if(dis[t] <= w) continue;
            dis[t] = w;
            q.push(xxx(t,w));
        }
    }
    return ans;
}
prim优先队列
struct xxx
{
    int from,to,w;
    xxx(int a,int b,int c):from(a),to(b),w(c){};
    friend bool operator <(xxx a,xxx b)
    {
        return a.w < b.w;
    }
};
vector<xxx> v;

int findd(int x)
{
    return pre[x] == x?x:findd(pre[x]);
}

//不连通返回-1,否则返回最小花费
bool kruskal()
{
    for(int i = 1;i <= n;i++)   pre[i] = i;
    int ans = 0,cnt = 0;
    sort(v.begin(),v.end());
    for(int i = 0;i < v.size();i++)
    {
        int x = findd(v[i].from),y = findd(v[i].to);
        if(x != y)
        {
            ans += v[i].w;
            pre[x] = y;
            cnt++;
        }
    }
    if(cnt < n-1)   return -1;
    return ans;
}
kruskal

矩阵树定理:

1.G的度数矩阵D[G]是一个n*n的矩阵,并且满足:当i≠j时,dij=0;当i=j时,dij等于vi的度数。

2.G的邻接矩阵A[G]也是一个n*n的矩阵, 并且满足:如果vi、vj之间有边直接相连,则aij=1,否则为0。

3.G的Kirchhoff矩阵(也称为拉普拉斯算子)C[G]=D[G]-A[G]。

则G的生成树个数等于C[G]任何一个n-1阶主子式的行列式的绝对值。

#include<bits/stdc++.h>
using namespace std;

int n,m,k,a[55][55];
long long g[55][55]; 

long long calc()
{
    long long ans = 1;
    for(int i = 1;i < n;i++)
    {
        for(int j = i+1;j < n;j++)
        {
            while(g[j][i] != 0)
            {
                long long t = g[i][i]/g[j][i];
                for(int k = i;k < n;k++)    g[i][k] -= t*g[j][k];
                for(int k = i;k < n;k++)    swap(g[i][k],g[j][k]);
                ans = -ans;
            }
        }
        if(g[i][i] == 0)    return 0;
        ans = ans*g[i][i];
    }
    return abs(ans);
}

int main()
{
    ios::sync_with_stdio(false);
    while(cin >> n >> m >> k)
    {
        memset(a,0,sizeof(a));
        memset(g,0,sizeof(g));
        while(m--)
        {
            int x,y;
            cin >> x >> y;
            a[x][y] = a[y][x] = 1;
        }
        for(int i = 1;i <= n;i++)
        {
            int t = 0;
            for(int j = 1;j <= n;j++)
            {
                if(i != j && !a[i][j])
                {
                    t++;
                    g[i][j] = -1;
                }
            }
            g[i][i] = t;
        }
        cout << calc() << endl;
    }
    return 0;
}
矩阵树
int n,vis[505],used[505],pre[505],cost[505],a[505][505],maxd[505][505];
//不连通返回-1,否则返回次小花费
int prim()
{
    int ans = 0;
    memset(vis,0,sizeof(vis));
    memset(used,0,sizeof(used));
    memset(maxd,0,sizeof(maxd));
    vis[1] = 1;
    for(int i = 2;i <= n;i++)   cost[i] = a[1][i];
    for(int i = 2;i <= n;i++)
    {
        int minn = INF;
        int k = -1;
        for(int j = 1;j <= n;j++)
        {
            if(!vis[j] && minn > cost[j])
            {
                minn = cost[j];
                k = j;
            }
        }
        if(minn == INF) return -1;
        ans += minn;
        vis[k] = 1;
        for(int j = 1;j <= n;j++)
        {
            if(vis[j])  maxd[j][k] = maxd[k][j] = max(maxd[j][pre[k]],cost[k]);
            if(!vis[j] && cost[j] > a[k][j])
            {
                cost[j] = a[k][j];
                pre[j] = k;
            }
        }
    }
    return ans;
}
次小生成树

有向图的强联通分量个数:

//O(n+m)
//缩点,belong保存每个点属于的强联通集合编号
int n,m,vis[10005],dfn[10005],low[10005],belong[10005],cnt,ans;
vector<int> v[10005];

void tarjan(int now)
{
    stack<int> s;
    s.push(now);
    vis[now] = 1;
    dfn[now] = low[now] = ++cnt;
    for(int i = 0;i < v[now].size();i++)
    {
        int t = v[now][i];
        if(dfn[t] == 0)
        {
            tarjan(t);
            low[now] = min(low[now],low[t]);
        }
        else if(vis[t])    low[now] = min(low[now],dfn[t]);
    }
    if(dfn[now] == low[now])
    {
        ans++;
        while(!s.empty())
        {
            int t = s.top();
            s.pop();
            vis[t] = 0;
            belong[t] = ans;
            if(t == now)    break;
        }
    }
}

int main()
{
    for(int i = 1;i <= n;i++)
    {
        if(dfn[i] == 0) tarjan(i);
    }
}
tarjan
//O(n+m)
#include<bits/stdc++.h>
using namespace std;

int n,m,vis1[10005],vis2[10005],ans;
vector<int> v1[10005],v2[10005];
stack<int> s;

void dfs1(int now)
{
    vis1[now] = 1;
    for(int i = 0;i < v1[now].size();i++)
    {
        int t = v1[now][i];
        if(vis1[t])  continue;
        dfs1(t);
    }
    s.push(now);
}

void dfs2(int now)
{
    vis2[now] = 1;
    for(int i = 0;i < v2[now].size();i++)
    {
        int t = v2[now][i];
        if(vis2[t]) continue;
        dfs2(t);
    }
}

int main()
{
    ios::sync_with_stdio(false);
    while(scanf("%d%d",&n,&m) &&(n+m))
    {
        for(int i = 1;i <= n;i++)
        {
            v1[i].clear();
            v2[i].clear();
        }
        memset(vis1,0,sizeof(vis1));
        memset(vis2,0,sizeof(vis2));
        while(!s.empty())   s.pop();
        ans = 0;
        while(m--)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            v1[x].push_back(y);
            v2[y].push_back(x);
        }
        for(int i = 1;i <= n;i++)
        {
            if(!vis1[i]) dfs1(i);
        }
        while(!s.empty())
        {
            if(!vis2[s.top()])
            {
                ans++;
                dfs2(s.top());
            }
            s.pop();
        }
        if(ans == 1)  printf("Yes
");
        else    printf("No
");
    }
    return 0;
}
kosaraju

无向图中一些基本概念:

割点集合:删除这个顶点集合,以及这个集合中所有顶点关联的边以后,原图变成多个连通块。
点连通度:最小割点集合中的顶点数。
割边集合:删除这个边集合以后,原图变成多个连通块。
边连通度:最小割边集合中的边数。

点双连通(双连通或重连通):无向连通图的点连通度大于1。
割点(关节点):当且仅当这个图的点连通度为1,则割点集合的唯一元素被称为割点。
边双连通(双连通或重连通):无向连通图的边连通度大于1。
桥(关节边 ):当且仅当这个图的边连通度为 1,则割边集合的唯一元素被称为桥。

边双连通分支:在连通分支中去掉一条边,连通分支仍连通。
点双连通分支(块):在连通分支中去掉一个点,连通分支仍连通。

struct bridge
{
    int u,int v;
    bridge(int a,int b):u(a),v(b){}
};
vector<bridge> ansb;
vector<int> absc;

//ansb保存桥,ansc保存割点
void tarjan(int now,int pre)
{
    low[now] = dfn[now] = ++cnt;
    int son = 0;
    for(int i = 0;i < v[now].size();i++)
    {
        int t = v[now][i];
        if(t == pre)    continue;
        if(!dfn[t])
        {
            son++;
            tarjan(t,now);
            low[now] = max(low[now],low[t]);
            //桥,(u,v)为树枝,low[v] > dfn[u]
            if(low[t] > dfn[now])
            {
                ansb.push_back(bridge(now,t));
                ansb.push_back(bridge(t,now));
            }
            //割点,(u,v)父子边,u不为树根,low[v] >= dfn[u]
            if(now != root && low[t] >= dfn[now]) ansc.push_back(t);
        }
        else    low[now] = max(low[now],low[t]);
    }
    //割点,u为根,分支数大于1
    if(now == root && son > 1)  ansc.push_back(now);
}
求割点和桥
去掉桥,其余的连通分支就是边双连通分支了。
一个有桥的连通图要变成双连通图的话,把双连通子图缩成一个点,形成一棵树。
再加上(leaf+1)/2条边。
求边双连通分支
//cutcnt表示个数
//block储存每个点连通分支
stack<int> s;
vector<int> block[10005];
void tarjan(int now,int pre)
{
    low[now] = dfn[now] = ++cnt;
    s.push(now);
    for(int i = 0;i < v[now].size();i++)
    {
        int t = v[now][i];
        if(t == pre)    continue;
        if(!dfn[t])
        {
            tarjan(t,now);
            low[now] = max(low[now],low[t]);
            if(low[t] >= dfn[now])
            {
                cutcnt++;
                block[cutcnt].clear();
                while(!s.empty())
                {
                    int tt = s.top();
                    s.pop();
                    block[cutcnt].push_back(tt);
                    if(tt = t)  break;
                }
                block[cutcnt].push_back(now);
            }
        }
        else    low[now] = max(low[now],low[t]);
    }
}
求点双连通分支

struct xxx
{
    int u,v,w;
}e[10005];
int n,m,a[55],sum[55],cnt,in[800],pre[800],vis[800],id[800];

void addedge(int u,int v,int w)
{
    e[cnt].u = u;
    e[cnt].v = v;
    e[cnt++].w = w;
}


//已处理重边,不存在最小树形图返回-1,否则返回最小值
int zhuliu(int root, int n, int m)
{
    int ans = 0;
    while(1)
    {
        //找最小入边
        for(int i = 0;i < n;i++)    in[i] = INF;
        for(int i = 0;  < m;i++)
        {
            int u = e[i].u;
            int v = e[i].v;
            if(e[i].w < in[v] && u != v)
            {
                pre[v] = u;
                in[v] = e[i].w;
            }     //去除自环和重边
        }
        for(int i = 0;i < n;i++)
        {
            if(i == root)   continue;
            if(in[i] == INF)    return -1;                                 //除了跟以外有点没有入边,则根无法到达它
        }
        int cnt = 0;                             //找环
        memset(id,-1,sizeof(id));
        memset(vis,-1,sizeof(vis));
        in[root] = 0;
        for(int i = 0;i < n;i++)
        {
            ans += in[i];
            int v = i;
            while(vis[v] != i && id[v] == -1 && v != root)  //每个点寻找其前序点,要么最终寻找至根部,要么找到一个环
            {
                vis[v] = i;
                v = pre[v];
            }
            if(v != root && id[v] == -1)//缩点
            {
                for(int u = pre[v];u != v;u = pre[u])   id[u] = cnt;
                id[v] = cnt++;
            }
        }
        if(cnt == 0)    break; //无环   则break
        for(int i = 0;i < n;i++)
        {
            if(id[i] == -1) id[i] = cnt++;
        }
        for(int i = 0;i < m;)
        {
            int u = e[i].u;
            int v = e[i].v;
            e[i].u = id[u];
            e[i].v = id[v];
            if(id[u] != id[v])  e[i++].w -= in[v];
            else    e[i]=e[--m];
        }
        n = cnt;
        root = id[root];
    }
    return ans;
}
最小树形图zhuliu

二分图:

最大匹配 == 最小点覆盖

最小路径覆盖 == 最大独立集 == 顶点数-最大匹配

最大团 == 补图最大独立集

int n,m,l;
int linker[505];  
bool used[505];  
int g[505][505];

bool dfs(int u)
{
    for(int v = 1;v <= m;v++)
    {
        if(g[u][v] && !used[v])
        {
            used[v] = 1;
            if(linker[v] == -1 || dfs(linker[v]))
            {
                linker[v] = u;
                return 1;
            }  
        }  
    }
    return 0;  
}  

int MaxMatch()
{  
    int ans = 0;  
    memset(linker,-1,sizeof(linker));  
    for(int i = 1;i <= n;i++)
    {  
        memset(used,0,sizeof(used));
        if(dfs(i))    ans++;
    }
    return ans;
}  
匈牙利邻接矩阵
//O(nl)
int n,m,l;
int linker[505];  
bool used[505];  
vector<int> v[505];

bool dfs(int u)
{
    for(int i = 0;i < v[u].size();i++)
    {
        int t = v[u][i];
        if(!used[t])
        {
            used[t] = 1;
            if(linker[t] == -1 || dfs(linker[t]))
            {
                linker[t] = u;
                return 1;
            }  
        }  
    }
    return 0;  
}  

int MaxMatch()
{  
    int ans = 0;  
    memset(linker,-1,sizeof(linker));  
    for(int i = 1;i <= n;i++)
    {  
        memset(used,0,sizeof(used));
        if(dfs(i))    ans++;
    }
    return ans;
}  
匈牙利邻接表
//O(V^0.5 E)
//适用于数据较大的二分匹配 

int g[MAXN][MAXN],Mx[MAXN],My[MAXN],Nx,Ny;
int dx[MAXN],dy[MAXN],dis;
bool vst[MAXN];
bool searchP()
{
    queue<int>Q;
    dis=INF;
    memset(dx,-1,sizeof(dx));
    memset(dy,-1,sizeof(dy));
    for(int i=0;i<Nx;i++)
        if(Mx[i]==-1)
        {
            Q.push(i);
            dx[i]=0;
        }  
    while(!Q.empty())
    {
        int u=Q.front();
        Q.pop();
        if(dx[u]>dis)  break;
        for(int v=0;v<Ny;v++)
            if(g[u][v]&&dy[v]==-1)
            {
                dy[v]=dx[u]+1;
                if(My[v]==-1)  dis=dy[v];
                else
                {
                    dx[My[v]]=dy[v]+1;
                    Q.push(My[v]);
                }    
            }    
    }  
    return dis!=INF;    
}    
bool DFS(int u)
{
    for(int v=0;v<Ny;v++)
       if(!vst[v]&&g[u][v]&&dy[v]==dx[u]+1)
       {
           vst[v]=1;
           if(My[v]!=-1&&dy[v]==dis) continue;
           if(My[v]==-1||DFS(My[v]))
           {
               My[v]=u;
               Mx[u]=v;
               return 1;
           }    
       }  
    return 0;  
}
int MaxMatch()
{
    int res=0;
    memset(Mx,-1,sizeof(Mx));
    memset(My,-1,sizeof(My));
    while(searchP())
    {
        memset(vst,0,sizeof(vst));
        for(int i=0;i<Nx;i++)
          if(Mx[i]==-1&&DFS(i))  res++;
    }
    return res;   
}    
Hopcroft-Carp
bool dfs(int u)
{
    for(int i = 0;i < v[u].size();i++)
    {
        int t = v[u][i];
        if(used[t]) continue;
        used[t] = 1;
        if(linker[t][0] < capacity[t])
        {
            linker[t][++linker[t][0]] = u;
            return 1;
        }
        for(int j = 1;j <= capacity[t];j++)
        {
            if(dfs(linker[t][j]))
            {
                linker[t][j] = u;
                return 1;
            }
        }
    }
        return 0;
}

int MaxMatch()
{
    int ans = 0;
    memset(linker,0,sizeof(linker));
    for(int i = 1;i <= n;i++)
    {
        memset(used,0,sizeof(used));
        if(dfs(i))    ans++;
    }
    return ans;
}
多重匹配匈牙利
int n,m,g[305][305],linker[305],lx[305],ly[305],slack[305],visx[305],visy[305];

bool dfs(int u)
{
    visx[u] = 1;
    for(int i = 1;i <= m;i++)
    {
        if(visy[i]) continue;
        int t = lx[u]+ly[i]-g[u][i];
        if(t == 0)
        {
            visy[i] = 1;
            if(!linker[i]|| dfs(linker[i]))
            {
                linker[i] = u;
                return 1;
            }
        }
        else    slack[i] = min(slack[i],t);
    }
    return 0;
}

int KM()
{
    memset(linker,0,sizeof(linker));
    memset(ly,0,sizeof(ly));
    for(int i = 1;i <= n;i++)
    {
        lx[i] = -INF;
        for(int j = 1;j <= m;j++)
        {
            if(g[i][j] > lx[i])  lx[i] = g[i][j];
        }
    }
    for(int i = 1;i <= n;i++)
    {
        memset(slack,0x3f,sizeof(slack));
        while(1)
        {
            memset(visx,0,sizeof(visx));
            memset(visy,0,sizeof(visy));
            if(dfs(i))   break;
            int d = INF;
            for(int j = 1;j <= m;j++)
            {
                if(!visy[j]) d = min(d,slack[j]);
            }
            for(int j = 1;j <= n;j++)
            {
                if(visx[j])  lx[j] -= d;
            }
            for(int j = 1;j <= m;j++)
            {
                if(visy[j])  ly[j] += d;
                else slack[j] -= d;
            }
        }
    }
    int ans = 0;
    for(int i = 1;i <= m;i++)
    {
        if(linker[i])   ans += g[linker[i]][i];
    }
    return ans;
}
二分图最大权KM

网络流:

最大流 == 最小割

//s为源点,t为汇点
//O(nm^2)
int n,m,s,t,a[20][20],pre[20],vis[20];

bool bfs()
{
    memset(pre,0,sizeof(pre));
    memset(vis,0,sizeof(vis));
    vis[s] = 1;
    queue<int> q;
    q.push(s);
    while(!q.empty())
    {
        int now = q.front();
        q.pop();
        if(now == t)    return 1;
        for(int i = 1;i <= n;i++)
        {
            if(vis[i])  continue;
            if(a[now][i])
            {
                q.push(i);
                pre[i] = now;
                vis[i] = 1;
            }
        }
    }
    return 0;
}

int EdmondsKarp()
{
   int ans = 0;
   while(bfs())
   {
       int minn = 1e9;
       for(int i = t;i != s;i = pre[i]) minn = min(minn,a[pre[i]][i]);
       for(int i = t;i != s;i = pre[i])
       {
           a[pre[i]][i] -= minn;
           a[i][pre[i]] += minn;
       }
       ans += minn;
   }
   return ans;
}
最大流EdmondsKarp
struct edge
{
    int u,v,c,next;
}e[20*N];
int n,m,sp,tp,cnt,head[N],d[N];
//源点汇点全局定义

void init()
{
    cnt = 0;
    memset(head,-1,sizeof(head));
}

void addedge(int u,int v,int c)
{
    e[cnt].u = u;e[cnt].v = v;e[cnt].c = c;
    e[cnt].next = head[u];head[u] = cnt++;
    e[cnt].u = v;e[cnt].v = u;e[cnt].c = 0;
    e[cnt].next = head[v];head[v] = cnt++;
}

int bfs()
{
    queue<int> q;
    memset(d,-1,sizeof(d));
    d[sp]=0;
    q.push(sp);
    while(!q.empty())
    {
        int cur = q.front();
        q.pop();
        for(int i = head[cur];i != -1;i = e[i].next)
        {
            int u = e[i].v;
            if(d[u] == -1 && e[i].c > 0)
            {
                d[u] = d[cur]+1;
                q.push(u);
            }
        }
    }
    return d[tp] != -1;
}

int dfs(int a,int b)
{
    int r = 0;
    if(a == tp) return b;
    for(int i = head[a];i != -1 && r < b;i = e[i].next)
    {
        int u = e[i].v;
        if(e[i].c > 0 && d[u] == d[a]+1)
        {
            int x = min(e[i].c,b-r);
            x = dfs(u,x);
            r += x;
            e[i].c -= x;
            e[i^1].c += x;
        }
    }
    if(!r)  d[a] =- 2;
    return r;
}

int dinic()
{
    int ans = 0,t;
    while(bfs())
    {
        while(t = dfs(sp,INF))
        ans += t;
    }
    return ans;
}
dinic
struct Edge
{
    int to,next,cap,flow;
} edge[M];
int n,m,cnt,head[N],gap[N],d[N],cur[N],que[N],p[N];

void init()
{
    cnt = 0;
    memset(head,-1,sizeof(head));
}

void addedge(int u,int v,int c)
{
    edge[cnt].to = v;
    edge[cnt].cap = c;
    edge[cnt].flow = 0;
    edge[cnt].next = head[u];
    head[u] = cnt++;
    edge[cnt].to = u;
    edge[cnt].cap = 0;
    edge[cnt].flow = 0;
    edge[cnt].next = head[v];
    head[v] = cnt++;
}

void bfs(int source,int sink)
{
    memset(d,-1,sizeof(d));
    memset(gap,0,sizeof(gap));
    gap[0] = 1;
    int front = 0,rear = 0;
    d[sink] = 0;
    que[rear++] = sink;
    while(front != rear)
    {
        int u = que[front++];
        for(int i = head[u];i != -1;i = edge[i].next)
        {
            int v = edge[i].to;
            if(d[v] != -1) continue;
            que[rear++] = v;
            d[v] = d[u]+1;
            gap[d[v]]++;
        }
    }
}

int isap(int source,int sink,int N)        //源点,汇点,总点数
{
    bfs(source,sink);
    memcpy(cur,head,sizeof(head));
    int top = 0,x = source,flow = 0;
    while(d[source] < N)
    {
        if(x == sink)
        {
            int Min = INF,inser;
            for(int i = 0;i < top;i++)
            {
                if(Min > edge[p[i]].cap-edge[p[i]].flow)
                {
                    Min = edge[p[i]].cap-edge[p[i]].flow;
                    inser = i;
                }
            }
            for(int i = 0;i < top;i++)
            {
                edge[p[i]].flow += Min;
                edge[p[i]^1].flow -= Min;
            }
            flow += Min;
            top = inser;
            x = edge[p[top]^1].to;
            continue;
        }
        int ok = 0;
        for(int i = cur[x];i != -1;i = edge[i].next)
        {
            int v = edge[i].to;
            if(edge[i].cap > edge[i].flow && d[v]+1 == d[x])
            {
                ok = 1;
                cur[x] = i;
                p[top++] = i;
                x = edge[i].to;
                break;
            }
        }
        if(!ok)
        {
            int Min = N;
            for(int i = head[x];i != -1;i = edge[i].next)
            {
                if(edge[i].cap > edge[i].flow && d[edge[i].to] < Min)
                {
                    Min = d[edge[i].to];
                    cur[x] = i;
                }
            }
            if(--gap[d[x]] == 0) break;
            gap[d[x] = Min+1]++;
            if(x != source) x = edge[p[--top]^1].to;
        }
    }
    return flow;
}
isap
int n,m,s,t,pre[1005],vis[1005],dis[1005],head[1005],cnt = 0;
struct ee
{
    int u,v,cap,cost,next;
}e[2005];

void add(int u,int v,int cap,int cost)
{
    e[cnt].u = u;
    e[cnt].v = v;
    e[cnt].cap = cap;
    e[cnt].cost = cost;
    e[cnt].next = head[u];
    head[u] = cnt++;
    e[cnt].u = v;
    e[cnt].v = u;
    e[cnt].cap = 0;
    e[cnt].cost = -cost;
    e[cnt].next = head[v];
    head[v] = cnt++;
}

bool spfa()
{
    memset(vis,0,sizeof(vis));
    memset(pre,-1,sizeof(pre));
    for(int i = s;i <= t;i++)   dis[i] = 1e9;
    vis[s] = 1;
    dis[s] = 0;
    queue<int> q;
    q.push(s);
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        vis[u] = 0;
        for(int i = head[u];i != -1;i = e[i].next)
        {
            int v = e[i].v;
            if(e[i].cap && dis[v] > dis[u]+e[i].cost)
            {
                dis[v] = dis[u]+e[i].cost;
                pre[v] = i;
                if(vis[v])  continue;
                q.push(v);
                vis[v] = 1;
            }
        }
    }
    if(dis[t] == 1e9)   return 0;
    return 1;
}
int mcmf()
{
    int flow = 0,ans = 0;
    while(spfa())
    {
        int minn = 1e9;
        for(int i = pre[t];i != -1;i = pre[e[i].u]) minn = min(e[i].cap,minn);
        for(int i = pre[t];i != -1;i = pre[e[i].u])
        {
            e[i].cap -= minn;
            e[i^1].cap += minn;
        }
        flow += minn;
        ans += dis[t]*minn;
    }
    return ans;
}
最小费用最大流

2-sat:

x == 1           : x' -> x    (x必取)

x and y == 1 : x' -> x ,y' -> y  (两个数必须全为1)

x and y == 0 : y -> x' ,x -> y'  (两个数至少有一个为0)

x or y == 1    : x' -> y ,y' -> x     (两个数至少有一个为1)

x or y == 0    : x -> x' ,y -> y'         (两个数全为0) 

x xor y == 1  : x -> y' ,y -> x' ,y' -> x ,x '-> y (两个数不同)

x xor y == 0  : x -> y ,x' -> y' ,y -> x ,y' -> x'  (两个数相同)

//可得到最小字典序的解
#include<bits/stdc++.h>
using namespace std;

int n,m,vis[16005];
vector<int> v[16005];
stack<int> s;

bool dfs(int u)
{
    if(vis[u^1])    return 0;
    if(vis[u])  return 1;
    vis[u] = 1;
    s.push(u);
    for(int i = 0;i < v[u].size();i++)
    {
        int t = v[u][i];
        if(!dfs(t)) return 0;
    }
    return 1;
}

bool twosat(int n)
{
    memset(vis,0,sizeof(vis));
    for(int i = 0;i < n;i += 2)
    {
        if(vis[i] || vis[i^1])  continue;
        while(!s.empty())   s.pop();
        if(!dfs(i))
        {
            while(!s.empty())
            {
                vis[s.top()] = 0;
                s.pop();
            }
            if(!dfs(i^1))   return 0;
        }
    }
    return 1;
}

int main()
{
    ios::sync_with_stdio(false);
    while(cin >> n >> m)
    {
        for(int i = 0;i < 2*n;i++)  v[i].clear();
        memset(vis,0,sizeof(vis));
        while(m--)
        {
            int a,b;
            cin >> a >> b;
            a--;
            b--;
            v[a].push_back(b^1);
            v[b].push_back(a^1);
        }
        if(twosat(2*n))
        {
            for(int i = 0;i < 2*n;i++)
            {
                if(vis[i])  cout << i+1 << endl;
            }
        }
        else    cout << "NIE" << endl;
    }
    return 0;
}
染色法
int n,m,vis[2*N],dfn[2*N],low[2*N],belong[2*N],pos[2*N],deg[2*N],ok[2*N],cnt,color;
vector<int> v[2*N],v2[2*N],ans;
stack<int> st;

//求任意解
void top(int n)
{
    ans.clear();
    memset(ok,0,sizeof(ok));
    queue<int> q;
    for(int i = 1;i <= color;i++)
    {
        if(deg[i] == 0) q.push(i);
    }
    while(!q.empty())
    {
        int now = q.front();
        q.pop();
        if(ok[now] == 0)
        {
            ok[now] = 1;
            ok[pos[now]] = 2;
        }
        for(int i = 0;i < v2[now].size();i++)
        {
            int t = v2[now][i];
            if(--deg[t] == 0)   q.push(t);
        }
    }
    for(int i = 0;i < n;i++)
    {
        if(ok[belong[i]] == 1)  ans.push_back(i);
    }
}

void tarjan(int now)
{
    st.push(now);
    vis[now] = 1;
    dfn[now] = low[now] = ++cnt;
    for(int i = 0;i < v[now].size();i++)
    {
        int t = v[now][i];
        if(dfn[t] == 0)
        {
            tarjan(t);
            low[now] = min(low[now],low[t]);
        }
        else if(vis[t])    low[now] = min(low[now],dfn[t]);
    }
    if(dfn[now] == low[now])
    {
        color++;
        while(!st.empty())
        {
            int t = st.top();
            st.pop();
            vis[t] = 0;
            belong[t] = color;
            if(t == now)    break;
        }
    }
}

bool solve(int n)
{
    for(int i = 0;i < n;i++)
    {
        if(dfn[i] == 0) tarjan(i);
    }
    for(int i = 0;i < n;i += 2)
    {
        if(belong[i] == belong[i^1])    return 0;
        pos[belong[i]] = belong[i^1];
        pos[belong[i^1]] = belong[i];
    }
    for(int i = 0;i < n;i++)
    {
        for(int j = 0;j < v[i].size();j++)
        {
            int t = v[i][j];
            if(belong[i] != belong[t])
            {
                deg[belong[i]]++;
                v2[belong[t]].push_back(belong[i]);
            }
        }
    }
    top(n);
    return 1;
}
tarjan缩点+拓扑排序

struct point
{
    int x,y,id;
    friend bool operator <(point a,point b)
    {
        if(a.x != b.x)  return a.x < b.x;
        return a.y < b.y;
    }
}p[10005];
struct Bit
{
    int minn,pos;
    void init()
    {
        minn = INF;
        pos = -1;
    }
}bit[10005];
struct edge
{
    int u,v,d;
    friend bool operator<(edge a,edge b)
    {
        return a.d < b.d;
    }
}e[40005];
int n,k,cnt,pre[10005],a[10005],b[10005];

int findd(int x)
{
    return pre[x] == x?x:findd(pre[x]);
}

void addedge(int u,int v,int d)
{
    e[++cnt].u = u;
    e[cnt].v = v;
    e[cnt].d = d;
}

int lowbit(int x)
{
    return x&-x;
}

void update(int i,int x,int pos)
{
    while(i > 0)
    {
        if(x < bit[i].minn)
        {
            bit[i].minn = x;
            bit[i].pos = pos;
        }
        i -= lowbit(i);
    }
}

int ask(int i,int m)
{
    int minn = INF,pos = -1;
    while(i <= m)
    {
        if(bit[i].minn < minn)
        {
            minn = bit[i].minn;
            pos = bit[i].pos;
        }
        i += lowbit(i);
    }
    return pos;
}

int dist(point a,point b)
{
    return abs(a.x-b.x)+abs(a.y-b.y);
}

void manhattan()
{
    cnt = 0;
    for(int dir = 0;dir < 4;dir++)
    {
        if(dir == 1 || dir == 3)
        {
            for(int i = 1;i <= n;i++)   swap(p[i].x,p[i].y);
        }
        else if(dir == 2)
        {
            for(int i = 1;i <= n;i++)   p[i].x = -p[i].x;
        }
        sort(p+1,p+1+n);
        for(int i = 1;i <= n;i++)   a[i] = b[i] = p[i].y-p[i].x;
        sort(b+1,b+1+n);
        int t = unique(b+1,b+n+1)-b-1;
        for(int i = 1;i <= t;i++)   bit[i].init();
        for(int i = n;i >= 1;i--)
        {
            int pos = lower_bound(b+1,b+1+t,a[i])-b+1;
            int ans = ask(pos,t);
            if(ans != -1)   addedge(p[i].id,p[ans].id,dist(p[i],p[ans]));
            update(pos,p[i].x+p[i].y,i);
        }
    }
}

//返回第k小的边长
int solve(int k)
{
    manhattan();
    for(int i = 1;i <= n;i++)   pre[i] = i;
    sort(e+1,e+1+cnt);
    for(int i = 1;i <= cnt;i++)
    {
        int u = e[i].u,v = e[i].v;
        int x = findd(u),y = findd(v);
        if(x != y)
        {
            pre[x] = y;
            if(--k == 0)    return e[i].d;
        }
    }
}
曼哈顿最小生成树

//g[i][j]存放关系图:i,j是否有边,match[i]存放i所匹配的点
//建图开始初始化g
//最终匹配方案为match
//复杂度O(n^3)
//点是从1到n的
bool g[MAXN][MAXN],inque[MAXN],inblossom[MAXN],inpath[MAXN];
int match[MAXN],pre[MAXN],base[MAXN];
queue<int> q;
//找公共祖先
int findancestor(int u,int v)
{
    memset(inpath,false,sizeof(inpath));
    while(1)
    {
        u = base[u];
        inpath[u] = 1;
        if(match[u] == -1)  break;
        u = pre[match[u]];
    }
    while(1)
    {
        v = base[v];
        if(inpath[v])   return v;
        v = pre[match[v]];
    }
}

//压缩花
void reset(int u,int anc)
{
    while(u != anc)
    {
        int v = match[u];
        inblossom[base[u]] = 1;
        inblossom[base[v]] = 1;
        v = pre[v];
        if(base[v] != anc)  pre[v] = match[u];
        u = v;
    }
}

void contract(int u,int v,int n)
{
    int anc = findancestor(u,v);
    memset(inblossom,0,sizeof(inblossom));
    reset(u,anc);
    reset(v,anc);
    if(base[u] != anc)  pre[u] = v;
    if(base[v] != anc)  pre[v] = u;
    for(int i = 1;i <= n;i++)
    {
        if(inblossom[base[i]])
        {
            base[i] = anc;
            if(!inque[i])
            {
                q.push_back(i);
                inque[i] = 1;
            }
        }
    }

}

bool bfs(int S,int n)
{
    for(int i = 0;i <= n;i++)   pre[i]=-1,inque[i]=0,base[i]=i;
    q.clear();
    q.push_back(S);
    inque[S] = 1;
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        for(int v = 1;v <= n;v++)
        {
            if(g[u][v] && base[v] != base[u] && match[u] != v)
            {
                if(v == S || match[v] !=-1 && pre[match[v]] != -1)  contract(u,v,n);
                else if(pre[v] == -1)
                {
                    pre[v] = u;
                    if(match[v] != -1)
                    {
                        q.push_back(match[v]);
                        inque[match[v]] = 1;
                    }
                    else
                    {
                        u = v;
                        while(u! = -1)
                        {
                            v = pre[u];
                            int w = match[v];
                            match[u] = v;
                            match[v] = u;
                            u = w;
                        }
                        return 1;
                    }
                }
            }
        }
    }
    return 0;
}

int solve(int n)
{
    memset(match,-1,sizeof(match));
    int ans = 0;
    for(int i=1;i<=n;i++)
    {
        if(match[i] == -1 && dfs(i,n))  ans++;
    }
    return ans;
}
一般图匹配带花树

void dfs(int now,int pre)
{
    dep[now] = dep[pre]+1;
    for(int i = 0;i < v[now].size();i++)
    {
        int t = v[now][i];
        if(t == pre)    continue;
        fa[t][0] = now;
        dfs(t,now);
    }
}

int lca(int x,int y)
{
    if(dep[x] < dep[y]) swap(x,y);
    int t = dep[x]-dep[y];
    for(int i = 0;i <= 20;i++)
    {
        if((1<<i)&t)    x = fa[x][i];
    }
    if(x == y)  return x;
    for(int i = 20;i >= 0;i--)
    {
        if(fa[x][i] != fa[y][i])
        {
            x = fa[x][i];
            y = fa[y][i];
        }
    }
    return fa[x][0];
}


int main()
{
    dfs(1,0);
    for(int i = 1;i <= 20;i++)
    {
        for(int j = 1;j <= n;j++)
        {
            fa[j][i] = fa[fa[j][i-1]][i-1];
        }
    }
}
最近公共祖先倍增lca
int n,m,f[N*2],rmq[N*2];
vector<int> v[N];

struct ST
{
    int mm[2*N],dp[2*N][20];
    void init(int n)
    {
        mm[0] = -1;
        for(int i = 1;i <= n;i++)
        {
            mm[i] = ((i&(i-1)) == 0)?mm[i-1]+1:mm[i-1];
            dp[i][0] = i;
        }
        for(int j = 1;j <= mm[n];j++)
        {
            for(int i = 1;i+(1<<j)-1 <= n;i++)  dp[i][j] = rmq[dp[i][j-1]] < rmq[dp[i+(1<<(j-1))][j-1]]?dp[i][j-1]:dp[i+(1<<(j-1))][j-1];
        }
    }
    int query(int a,int b)
    {
        if(a > b)   swap(a,b);
        int k = mm[b-a+1];
        return rmq[dp[a][k]] <= rmq[dp[b-(1<<k)+1][k]]?dp[a][k]:dp[b-(1<<k)+1][k];
    }
}st;

void dfs(int now,int pre,int dep)
{
    f[++cnt] = now;
    rmq[cnt] = dep;
    p[now] = cnt;
    for(int i = 0;i < v[now].size();i++)
    {
        int t = v[now][i];
        if(t == pre)    continue;
        dfs(t,now,dep+1);
        f[++cnt] = now;
        rmq[cnt] = dep;
    }
}

void initlca(int root,int n)
{
    cnt = 0;
    dfs(root,root,0);
    st.init(2*n-1);
}

int querylca(int x,int y)
{
    return f[st.query(p[x],p[y])];
}
lca+rmq

欧拉路径:

①无向图:连通,每个顶点度数都为偶数或者仅有两个点的度数为奇数。

②有向图:底图连通,所有顶点的出度 == 入度 或者 有且仅有一个顶点 出度 == 入度+1,有且仅有一个顶点 入度 == 出度+1。

③混合图:底图连通,存在欧拉回路,则一定存在欧拉路径。否则如果有且仅有两个点的(出度-入度)是奇数,那么给这两个点加边,判断是否存在欧拉回路。

欧拉回路:

①无向图:连通,每个顶点度数都为偶数。

②有向图:底图连通,每个顶点出度等于入度。

③混合图:底图连通,网络流判断。

vector<int> ans;

struct edge
{
    int to,next,id,dir,flag;
}e[20005];

void addedge(int u,int v,int id)
{
    e[cnt].to = v;
    e[cnt].id = id;
    e[cnt].dir = 0;
    e[cnt].flag = 0;
    e[cnt].next = head[u];
    head[u] = cnt++;
    e[cnt].to = u;
    e[cnt].id = id;
    e[cnt].dir = 1;
    e[cnt].flag = 0;
    e[cnt].next = head[v];
    head[v] = cnt++;
}

vector<edge> ans;
void dfs(int u)
{
    for(int i = head[u];i != -1;i = e[i].next)
    {
        if(!e[i].flag)
        {
            e[i].flag = 1;
            e[i^1].flag = 0;
            dfs(e[i].to);
            ans.push_back(i);
        }
    }
}
无向图欧拉路径
#define MAXN 250

vector<int> ans;

struct edge
{
    int to,next,id,flag;
}e[20005];

void addedge(int u,int v,int id)
{
    e[cnt].to = v;
    e[cnt].id = id;
    e[cnt].flag = 0;
    e[cnt].next = head[u];
    head[u] = cnt++;
}

vector<edge> ans;
void dfs(int u)
{
    for(int i = head[u];i != -1;i = e[i].next)
    {
        if(!e[i].flag)
        {
            e[i].flag = 1;
            dfs(e[i].to);
            ans.push_back(i);
        }
    }
}
有向图欧拉路径
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#define INF 0x3f3f3f3f
#define N 205
using namespace std;

struct edge
{
    int u,v,cap,next;
}e[20005];
int n,m,cnt,head[N],dep[N],gap[N],cur[N],st[N],que[N],ind[N],outd[N];

void addedge(int u,int v,int cap)
{
    e[cnt].u = u;
    e[cnt].v = v;
    e[cnt].cap = cap;
    e[cnt].next = head[u];
    head[u] = cnt++;
    e[cnt].u = v;
    e[cnt].v = u;
    e[cnt].cap = 0;
    e[cnt].next = head[v];
    head[v] = cnt++;
}

void bfs(int des)
{
    memset(dep,-1,sizeof(dep));
    memset(gap,0,sizeof(gap));
    gap[0] = 1;
    int front = 0, rear = 0;
    dep[des] = 0;
    que[rear++] = des;
    int u,v;
    while(front != rear)
    {
        u = que[front++];
        front %= N;
        for(int i = head[u];i != -1;i = e[i].next)
        {
            v = e[i].v;
            if(e[i].cap != 0 || dep[v] != -1)   continue;
            que[rear++] = v;
            rear %= N;
            ++gap[dep[v] = dep[u]+1];
        }
    }
}

int isap(int src,int des,int n)//源点、汇点、图中点的总数
{
    bfs(des);
    int ans = 0,top = 0;
    memcpy(cur,head,sizeof(head));
    int u = src,i;
    while(dep[src] < n)
    {
        if(u == des)
        {
            int temp = INF,inser = n;
            for(i = 0;i != top;i++)
            {
                if (temp > e[st[i]].cap)
                {
                    temp = e[st[i]].cap;
                    inser = i;
                }
            }
            for(i = 0;i != top;i++)
            {
                e[st[i]].cap -= temp;
                e[st[i]^1].cap += temp;
            }
            ans += temp;
            top = inser;
            u = e[st[top]].u;
        }
        if(u != des && !gap[dep[u]-1])  break;
        for(i = cur[u];i != -1;i = e[i].next)
        {
            if(e[i].cap != 0 && dep[u] == dep[e[i].v]+1)  break;
        }
        if(i != -1)
        {
            cur[u] = i;
            st[top++] = i;
            u = e[i].v;
        }
        else
        {
            int minn = n;
            for(i = head[u];i != -1;i = e[i].next)
            {
                if(!e[i].cap)    continue;
                if(minn > dep[e[i].v])
                {
                    minn = dep[e[i].v];
                    cur[u] = i;
                }
            }
            --gap[dep[u]];
            ++gap[dep[u] = minn+1];
            if(u != src)    u = e[st[--top]].u;
        }
    }
    return ans;
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        cnt = 0;
        memset(head,-1,sizeof(head));
        memset(ind,0,sizeof(ind));
        memset(outd,0,sizeof(outd));
        while(m--)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            outd[x]++;
            ind[y]++;
            if(z == 0)  addedge(x,y,1);
        }
        int sum = 0,flag = 1;
        for(int i = 1;i <= n;i++)
        {
            if(outd[i]-ind[i] > 0)
            {
                addedge(0,i,(outd[i]-ind[i])/2);
                sum += (outd[i]-ind[i])/2;
            }
            else    if(ind[i]-outd[i] > 0)  addedge(i,n+1,(ind[i]-outd[i])/2);
            if((outd[i]-ind[i])%2)  flag = 0;
        }
        if(flag && isap(0,n+1,n+2) == sum)    printf("possible
");
        else    printf("impossible
");
    }
    return 0;
}
混合图欧拉回路判断

原文地址:https://www.cnblogs.com/zhurb/p/7341261.html