算法笔记—搜索与图论

D F S DFS DFS

D F S DFS DFS又称暴搜,其中最重要的是回溯剪枝 D F S DFS DFS是不断递归,剪枝是当递归到某一层时,我们判断出一定不是我们所需要的答案,就可以提前结束当前递归避免无效计算;回溯是我们结束当前递归,需要返回上一层时,需要把我们的状态恢复到上一层,以便进行其他情况的搜索。

在这里插入图片描述

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

const int N = 20;

int n;
char g[N][N];
bool col[N], dg[N], udg[N];

void dfs(int u)
{
    if(u == n)
    {
        for(int i = 0; i < n; i ++)
        {
            for(int j = 0; j < n; j ++) cout << g[i][j];
            cout << endl;
        }
        
        cout << endl;
        return;
    }
    
    for(int i = 0; i < n; i ++)
    {
        if(!col[i] && !dg[u + i] && !udg[u - i + n])
        {
            g[u][i] = 'Q';
            col[i] = dg[u + i] = udg[u - i + n] = true;
            dfs(u + 1);
            col[i] = dg[u + i] = udg[u - i + n] = false;
            g[u][i] = '.';
        }
    }
    return;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> n;
    
    for(int i = 0; i < n; i ++)
        for(int j = 0; j < n; j ++)
            g[i][j] = '.';
    
    dfs(0);
    
    return 0;
}

B F S BFS BFS

B F S BFS BFS又称宽搜,是逐层搜索,当遍历完某一层所有点之后才会遍历下一层,常用队列实现。当图中的权重都为1或某一定值时, B F S BFS BFS搜索具有最短性,即第一次搜索到的点为距离起点最近的点
在这里插入图片描述

在这里插入图片描述

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

typedef pair<int, int> PII;
const int N = 110;

int g[N][N], d[N][N];
int n, m;
queue<PII> q;

int bfs()
{
    int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, -1, 1};
    
    while(q.size())
    {
        auto t = q.front();
        q.pop();
        
        for(int i = 0; i < 4; i ++)
        {
            int x = t.first + dx[i], y = t.second + dy[i];
            if(x >= 0 && x < n && y >= 0 && y < m && d[x][y] == -1 && g[x][y] == 0)
            {
                d[x][y] = d[t.first][t.second] + 1;
                q.push({x, y});
            }
        }
    }
    
    return d[n - 1][m - 1];
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> n >> m;
    
    for(int i = 0; i < n; i ++)
    {
        for(int j = 0; j < m; j ++)
        {
            cin >> g[i][j];
        }
    }
    
    memset(d, -1, sizeof d);
    d[0][0] = 0;
    q.push({0,0});
    
    cout << bfs() << endl;
    
    return 0;
}

树与图的存储与遍历

树是一种特殊的图,即无环连通图,因此树的存储与遍历与图一样,而无向图可以看成是双向连通的有向图,因此我们只考虑有向图即可。有向图的存储方式主要有两种,分别为邻接矩阵邻接表,其中邻接矩阵实现方式主要是二维数组,适合存储稠密图;邻接表实现方式主要是单链表,适合存储稀疏图。
在这里插入图片描述

树与图的深度优先遍历

在这里插入图片描述
在这里插入图片描述
我们可以求出删掉每个节点以后,剩余的连通部分节点数的最大值,在求出其中的最小值即可。对于每一个节点,我们都求出以该节点为根节点的子树的节点数,递归求解即可。

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

const int N = 100010;

int n;
int ans = N;
bool str[N];
int h[N], e[2 * N], ne[2 * N], idx;

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

int dfs(int u)
{
    str[u] = true;
    
    int sum = 1, res = 0;
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(!str[j])
        {
            int s = dfs(j);//递归求出每个子树的节点数
            res = max(res, s);//记录子树中最大的节点数
            sum += s;//计算当前根节点的节点数
        }
    }
    
    res = max(res, n - sum);//与剩下部分的节点数比较
    ans = min(ans, res);//更新答案
    
    return sum;//返回当前根节点的节点数
}
int main()
{
    cin >> n;
    
    memset(h, -1, sizeof h);
    for(int i = 0; i < n - 1; i ++)
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
        add(b, a);
    }
    
    dfs(1);
    
    cout << ans << endl;
    return 0;
}

树与图的广度优先遍历

在这里插入图片描述

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

const int N = 100010;

int h[N], e[N], ne[N], idx;
int d[N];
bool st[N];
int n, m;

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

int bfs()
{
    queue<int> q;
    
    q.push(1);
    while(q.size())
    {
        int t = q.front();
        q.pop();
        st[t] = true;
        
        for(int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if(!st[j])
            {
                d[j] = d[t] + 1;
                st[j] = true;
                q.push(j);
            }
        }
    }
    
    return d[n];
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> n >> m;
    
    memset(h, -1, sizeof h);
    memset(d, -1, sizeof d);
    
    for(int i = 0; i < m; i ++)
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
    }
    
    d[1] = 0;
    
    cout << bfs() << endl;
    
    return 0;
    
}

拓扑排序

若一个由图中所有点构成的序列 A A A满足:对于图中的每条边 ( x , y ) (x,y) (x,y) x x x A A A中都出现在 y y y 之前,则称 A A A 是该图的一个拓扑序列。
如果一个图是有向无环图,那么一定存在一个拓扑序,因此有向无环图又称为拓扑图
一个有向无环图一定至少存在一个入度为 0 0 0的点,因此求拓扑序可以从入度为零的点开始,依次遍历所有出边,并删除(保证其指向的点入度为零),再将入度为零的点入队
在这里插入图片描述

在这里插入图片描述

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

const int N = 100010;

int n, m;
int h[N], e[N], ne[N], idx;
int d[N];
int q[N], hh, tt = -1;

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

bool topsort()
{
    for(int i = 1; i <= n; i ++)
    {
        if(!d[i]) q[++ tt] = i;
    }
    
    while(hh <= tt)
    {
        int t = q[hh ++];
        
        for(int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            d[j] --;
            if(!d[j]) q[++ tt] = j;
        }
    }
    
    if(tt == n - 1) return true;
    else return false;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> n >> m;
    
    memset(h, -1, sizeof h);
    for(int i = 0; i < m; i ++)
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
        d[b] ++;
    }
    
    if(topsort())
    {
        for(int i = 0; i <= tt; i ++) cout << q[i] << ' ';
    }
    else cout << "-1" << endl;
    
    return 0;
}

最短路

在这里插入图片描述

D i j k s t r a Dijkstra Dijkstra

D i j k s t r a Dijkstra Dijkstra算法是基于贪心实现的,每次选取到起点最近的点,更新起点到其他点的距离,当遍历完所有点时,即得到了起点到其他点的最短路。朴素 D i j k s t r a Dijkstra Dijkstra时间复杂度为 O ( n 2 ) O(n^2) O(n2),主要应用于稠密图,一般使用邻接矩阵存储图。
在这里插入图片描述

在这里插入图片描述

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

const int N = 510;

int g[N][N];
int n, m;
int dist[N];
bool st[N];

int dijkstra(int u)
{
    dist[u] = 0;
    
    for(int i = 1; i <= n; i ++)
    {
        int t = -1;
        for(int j = 1; j <= n; j ++)
            if(!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;//从还未确定最短路径的点中找到最短
        st[t] = true;//该点到起点就是最短的
        
        for(int j = 1; j <= n; j ++)
            dist[j] = min(dist[j], dist[t] + g[t][j]);//用已找到的点更新其他点到起点的距离
    }
    
    return dist[n];
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> n >> m;
    
    memset(dist, 0x3f, sizeof dist);
    memset(g, 0x3f, sizeof g);
    
    for(int i = 0; i < m; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = min(g[a][b], c);
    }
    
    int t = dijkstra(1);
    
    if(t == 0x3f3f3f3f) cout << "-1" << endl;
    else cout << t << endl;
    
    return 0;
}

堆优化的 D i j k s t r a Dijkstra Dijkstra时间复杂度为 O ( m l o g ( n ) ) O(mlog(n)) O(mlog(n)),主要应用于稀疏图,一般使用邻接表存储图。

在这里插入图片描述

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

const int N = 200010;
typedef pair<int, int> PII;

int n, m;
int h[N], e[N], ne[N], w[N], idx;
int dist[N];
bool st[N];
priority_queue<PII, vector<PII>, greater<PII>> heap;//小根堆

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

int dijkstra(int u)
{
    dist[1] = 0;
    heap.push({0,1});
    
    while(heap.size())//遍历所有点
    {
        auto t = heap.top();//取出当前距离起点最近的点
        heap.pop();
        
        int ver = t.second, distance = t.first;
        if(!st[ver])
        {
            for(int i = h[ver]; i != -1; i = ne[i])
            {
                int j = e[i];
                if(dist[j] > distance + w[i])
                {
                    dist[j] = distance + w[i];//更新该点的所有出边
                    heap.push({dist[j], j});//将更新的点放进堆中
                }
            }
            st[ver] = true;
        }
    }
    
    if(dist[n] == 0x3f3f3f3f) return -1;
    else return dist[n];
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    cin >> n >> m;
    
    memset(dist, 0x3f, sizeof dist);
    memset(h, -1, sizeof h);
    for(int i = 0; i < m; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    
    cout << dijkstra(1) << endl;
    
    return 0;
}

B e l l m a n − f o r d Bellman-ford Bellmanford

B e l l m a n − f o r d Bellman-ford Bellmanford算法是每次求经过 k k k条边起点到终点的最短距离, k k k最多是 n n n,这里需要注意,如果枚举所有边,则可能出现起点到不了终点,但是终点的路径仍然有可能会被更新。使用该算法还需要注意我们在更新其他点时,有可能会出现使用已经更新过的其他点,为了避免这种情况发生,我们每次需要将上一次的结果保存下来,只使用上一次的结果更新其他点。
在这里插入图片描述

在这里插入图片描述

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

const int N = 510, M = 100010;

using namespace std;

int n, m, k;
int dist[N], backup[N];

struct edge{
    int a, b, c;
}Edge[M];

void bellmand()
{
    dist[1] = 0;
    
    for(int i = 0; i < k; i ++)
    {
        memcpy(backup, dist, sizeof dist);
        
        for(int j = 0; j < m; j ++)
        {
            int a = Edge[j].a, b = Edge[j].b, c = Edge[j].c;
            dist[b] = min(dist[b], backup[a] + c);
        }
    }
}

int main()
{
    cin >> n >> m >> k;
    
    memset(dist, 0x3f, sizeof dist);
    
    for(int i = 0; i < m; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        Edge[i] = {a, b, c};
    }
    
    bellmand();
    
    if(dist[n] > 0x3f3f3f3f / 2) cout << "impossible" << endl;
    else cout << dist[n] << endl;
    
    return 0;
}

s p f a spfa spfa

s p f a spfa spfa算法是将 B e l l m a n − f o r d Bellman-ford Bellmanford优化,在我们更新点的距离时,并不需要遍历所有边,只需要将更新过的点所能到达的点更新即可,即只有自己更新之后,自己所到达的点才可能会被更新。因此我们使用队列来优化,依次将更新过的点入队,然后将队头出队,遍历队头所能到达的点,将被更新的点再入队,这里需要注意,如果更新过的点已经在队列里面了,则只需要更新距离即可,不需要重复入队。
在这里插入图片描述

在这里插入图片描述

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

const int N = 100010;

int n, m;
int h[N], w[N], e[N], ne[N], idx;
int dist[N], backup[N];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

void spfa()
{
    queue<int> q;
    
    dist[1] = 0;
    st[1] = true;
    q.push(1);
    
    while(q.size())
    {
        int t = q.front();
        q.pop();
        st[t] = false;
        
        for(int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if(dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                if(!st[j]) //已更新过的点不需要重复入队
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
}
int main()
{
    cin >> n >> m;
    
    memset(h, -1, sizeof h);
    memset(dist, 0x3f, sizeof dist);
    
    for(int i = 0; i < m; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    
    spfa();
    
    if(dist[n] == 0x3f3f3f3f) cout << "impossible" << endl;
    else cout << dist[n] << endl;
    
    return 0;
}

我们可以使用一个数组来存一下当前的边数,当我们更新点的距离时,实际上也更新了一条路径,有一条新的路径使得起点到该点的距离更小,我们记录一下当前路径的边数,如果边数大于 n n n,说明存在环,且可以更新最短距离,说明一定存在负权回路。
在这里插入图片描述

在这里插入图片描述
分析题解

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

const int N = 2010, M = 100010;

int dist[N], cnt[N];
int n, m;
bool st[N];
int h[N], w[M], e[M], ne[M], idx;

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}

bool spfa()
{
    queue<int> q;
    
   
    //将所有点入队
    for(int i = 1; i <= n; i ++)
    {
        q.push(i);
        st[i] = true;
    }
    
    while(q.size())
    {
        int t = q.front();
        q.pop();
        st[t] = false;
        
        for(int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if(dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                
                //更新了n次,即有n条边,n + 1个点,即存在负环
                if(cnt[j] >= n) return true;
                
                if(!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }
    
    return false;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0);
    
    cin >> n >> m;
    
     memset(h, -1, sizeof h);
    
    for(int i = 0; i < m; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
    }
    
    if(spfa()) cout << "Yes" << endl;
    else cout << "No" << endl;
    
    return 0;
}

F l o y d Floyd Floyd

f l o y d floyd floyd算法是计算多源汇最短路的常用算法,其原理是基于动态规划,计算出任意两点之间的最短距离,时间复杂度为 O ( n 3 ) O(n^3) O(n3)。 在计算 i , j i, j i,j之间的最短距离时,考虑选取 1 1 1 k k k的节点作为中间经过的节点时,从 i i i j j j的最短路径的长度。

在这里插入图片描述
题解分析

/*
    比如,f[1][i][j]就代表了,在考虑了1节点作为中间经过的节点时,从i到j的最短路径的长度。
    分析可知,f[1][i][j]的值无非就是两种情况,而现在需要分析的路径也无非两种情况,i=>j,i=>1=>j:
    【1】f[0][i][j]:i=>j这种路径的长度,小于,i=>1=>j这种路径的长度
    【2】f[0][i][1]+f[0][1][j]:i=>1=>j这种路径的长度,小于,i=>j这种路径的长度
    形式化说明如下:
    f[k][i][j]可以从两种情况转移而来:
    【1】从f[k−1][i][j]转移而来,表示i到j的最短路径不经过k这个节点
    【2】从f[k−1][i][k]+f[k−1][k][j]转移而来,表示i到j的最短路径经过k这个节点

    总结就是:f[k][i][j]=min(f[k−1][i][j],f[k−1][i][k]+f[k−1][k][j])
    从总结上来看,发现f[k]只可能与f[k−1]有关。
*/
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 210;

int n, m, k;
int d[N][N];

void floyd()
{
    for(int k = 1; k <= n; k ++)
    {
        for(int i = 1; i <= n; i ++)
        {
            for(int j = 1; j <= n; j ++) d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
        }
    }
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0);
    
    cin >> n >> m >> k;
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= n; j ++)
        {
            if(i == j) d[i][j] = 0;
            else d[i][j] = 0x3f3f3f3f;
        }
    }
    
    for(int i = 0; i < m; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        d[a][b] = min(d[a][b], c);
    }
    
    floyd();
    
    for(int i = 0; i < k; i ++)
    {
        int a, b;
        cin >> a >> b;
        if(d[a][b] > 0x3f3f3f3f / 2) cout << "impossible" << endl;
        else cout << d[a][b] << endl;
    }
    
    return 0;
}

最小生成树

在这里插入图片描述
一般稠密图用朴素版 P r i m Prim Prim,稀疏图用 K r u s k a l Kruskal Kruskal算法

P r i m Prim Prim

p r i m prim prim算法基于贪心策略,每次选取距离当前生成树权值最小的点,将该点加到生成树中,并更新其他点到当前生成树的距离,当把所有的点都加到树中时,所得到的树就是最小生成树。
在这里插入图片描述

在这里插入图片描述
分析题解

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

const int N = 510, INF = 0x3f3f3f3f;

int dist[N], g[N][N];
bool st[N];
int n, m;

int prim()
{
    int res = 0;
    for(int i = 0; i < n; i ++)
    {
        int t = -1;
        for(int j = 1; j <= n; j ++)
        {
            if(!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        }
        
        st[t] = true;
        
        if(i && dist[t] == 0x3f3f3f3f) return INF;
        if(i) res += dist[t];
        
        for(int j = 1; j <= n; j ++) dist[j] = min(dist[j], g[t][j]);
    }
    
    return res;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0);
    
    cin >> n >> m;
    memset(dist, 0x3f, sizeof dist);
    memset(g, 0x3f, sizeof g);
    
    for(int i = 0; i < m; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        g[a][b] = g[b][a] = min(g[a][b], c);
    }
    
    int t = prim();
    if(t == INF) cout << "impossible" << endl;
    else cout << t << endl;
    
    return 0;
    
}

K r u s k a l Kruskal Kruskal

k r u s k a l kruskal kruskal算法思路是从小到大因此枚举所有边,每次尝试将该边加进当前的生成树中,当遍历完所有的边,所得到的生成树就是最小生成树。其中,每次尝试将一条边加进当前的生成树时,需要判断是否会构成环,即判断该边的两个顶点是否在一个连通块中,可以使用并查集判断。
在这里插入图片描述

在这里插入图片描述

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

const int N = 200010;

int n, m;
int p[N];

struct Edge
{
    int a, b, c;
    
    bool operator< (const Edge& W)const
    {
        return c < W.c;
    }
}edges[N];

int find(int x)
{
    if(p[x] != x) p[x] = find(p[x]);
    return p[x];
    

}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0);
    
    cin >> n >> m;
    
    for(int i = 0; i < m; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        edges[i] = {a, b, c};
    }
    
    sort(edges, edges + m);
    
    for(int i = 1; i <= n; i ++) p[i] = i;
    
    int res = 0, cnt = 0;
    for(int i = 0; i < m; i ++)
    {
        int a = edges[i].a, b = edges[i].b, c = edges[i].c;
        
        a = find(a), b = find(b);
        if(a != b)
        {
            res += c;
            cnt ++;
            p[b] = a;
        }
    }
    
    if(cnt < n - 1) cout << "impossible" << endl;
    else cout << res << endl;
    
    return 0;
}

二分图

染色法判定二分图

染色法判定二分图对于每个节点,我们 d f s dfs dfs其所在的连通块,将连通块染色,如果染色失败则不是二分图

在这里插入图片描述

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

const int N = 100010, M = 200010;

int h[N],ne[M], e[M], idx;
int color[N];
int n, m;

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

bool dfs(int x, int u)
{
   color[x] = u;
   
   for(int i = h[x]; i != -1; i = ne[i])
   {
       int j = e[i];
       if(!color[j])//若未颜色
       {
           //判断是否可以将其染色成功
           if(!dfs(j, 3 - u)) return false;
       }
       else if(color[j] == color[x]) return false;
   }
   
   return true;
}


int main()
{
    ios::sync_with_stdio(false), cin.tie(0);
    
    cin >> n >> m;
    
    memset(h, -1, sizeof h);
    
    for(int i = 0; i < m; i ++)
    {
        int a, b;
        cin >> a >> b;
        add(a, b), add(b,a);
    }
    
    bool flag = true;
    
    for(int i = 1; i <= n; i ++)
    {
        if(!color[i])
        {
            if(!dfs(i, 1))
            {
                flag = false;
            }
        }
    }
    
    if(flag) cout << "Yes" << endl;
    else cout << "No" << endl;
    
    return 0;
}

二分图最大匹配

匈牙利算法

匈牙利算法是对于二分图的一个顶点,判断是否可以匹配,若可以匹配则匹配;若不可以匹配,则判断要匹配的点是否可以更换匹配,当遍历完所有点,即得到二分图的最大匹配。
在这里插入图片描述

在这里插入图片描述
分析题解

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

const int N = 510, M = 200010;

int h[N], e[M], ne[M], idx;
int match[N];
bool st[N];
int n, m, k;

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

bool find(int u)
{
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        
        if(st[j]) continue;
        st[j] = true; //当前的节点一定是有对象的
        
        //如果没有,就匹配给当前的节点,如果已经有对象了,尝试让对方换一个
        if(match[j] == 0 || find(match[j]))
        {
            match[j] = u;
            return true;
        }
    }
    return false;
}

int main()
{
    ios::sync_with_stdio(false), cin.tie(0);
    
    cin >> n >> m >> k;
    
    memset(h, -1, sizeof h);
    
    for(int i = 0; i < k; i ++)
    {
        int a, b;
        cin >> a >> b;
        add(a, b);
    }
    
    int res = 0;
    for(int i = 1; i <= n; i ++)
    {
        memset(st, false, sizeof st);
        if(find(i)) res ++;
    }
    
    cout << res << endl;
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值