图论...

稀疏图和稠密图

数据结构中对于稀疏图的定义为:m 远小于n的平方的图称为稀疏图,m 远小于n的平方相近,称为稠密图
n为点数,m为边数

邻接矩阵

g [ N ] [ N ] g[N][N] g[N][N]

邻接表

// 对于每个点k,开一个单链表,存储k所有可以走到的点。h[k]存储这个单链表的头结点
int h[N], e[N], ne[N], idx;

// 添加一条边a->b
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

// 初始化
idx = 0;
memset(h, -1, sizeof h);

int dfs(int u)
{
    st[u] = true; // st[u] 表示点u已经被遍历过

    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j]) dfs(j);
    }
}
void bfs(){
	queue<int> q;
	st[1] = true; // 表示1号点已经被遍历过
	q.push(1);
	
	while (q.size())
	{
	    int t = q.front();
	    q.pop();
	
	    for (int i = h[t]; i != -1; i = ne[i])
	    {
	        int j = e[i];
	        if (!st[j])
	        {
	            st[j] = true; // 表示点j已经被遍历过
	            q.push(j);
	        }
	    }
	}
}

有向图

存有向边a->b,对于一张有向图,一般有邻接矩阵和邻接表的存储方式

无向图

对于无向图中的边ab,存储两条有向边a->b, b->a。

拓扑排序

  • 先把所有入度为0的点入队
  • 当队列不为空,执行以下操作
  • 获得队头,弹出队头
  • 枚举队头的所有出边t,t的入度减1,如t的入度为0,则加入队列

acwing848

/**
 * 先把所有入度为0的点入队
 * 当队列不为空,执行以下操作
 * 获得队头,弹出队头
 * 枚举队头的所有出边t,t的入度减1,如t的入度为0,则加入队列
**/

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 1e5 + 10;
int n, m;
int d[N], st[N], a[N], k;
int e[N], ne[N], h[N], idx;

void add(int a, int b)  // 添加一条边a->b
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void topSort(){
    k = 0;
    queue<int> q;
    //入度为0的点入队
    for (int i = 1; i <= n; i ++ ) if(d[i] == 0) q.push(i);
    while (q.size() > 0){
        int p = q.front();
        q.pop();
        //若之前被遍历过就继续
        if(st[p]) continue;
        st[p] = true;
        //记录拓扑序
        a[++k] = p;
        //枚举所有出边,入度减1,为0入队
        for (int i = h[p]; i != -1; i = ne[i]){
            int j = e[i];
            d[j] -= 1;
            if(d[j] == 0) q.push(j);
        }
    }
}

int main()
{
    memset(h, -1, sizeof h);
    idx = 0;
    
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i ++ ){
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
        d[b] += 1;
    }
    topSort();
    if(k != n) printf("%d", -1);
    else{
        for (int i = 1; i <= k; i ++ ){
            printf("%d ", a[i]);
        }
    }
}

最短路

朴素dijkstra算法

1. 1号点到每个点的最短距离
2. 边权不能为负

//时间复杂是 O(n^2+m)O(n2+m), n 表示点数,m 表示边数
int g[N][N];  // 存储每条边
int dist[N];  // 存储1号点到每个点的最短距离
bool st[N];   // 存储每个点的最短路是否已经确定

// 求1号点到n号点的最短路,如果不存在则返回-1
int dijkstra()
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for (int i = 0; i < n - 1; i ++ )
    {
        int t = -1;     // 在还未确定最短路的点中,寻找距离最小的点
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))
                t = j;

        // 用t更新其他点的距离
        for (int j = 1; j <= n; j ++ )
            dist[j] = min(dist[j], dist[t] + g[t][j]);

        st[t] = true;
    }

    if (dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

acwing849

/**
 * dist数组存点1到每个点的最短距离
 * 第一层循环n-1次,第二层循坏找dist中最小值的下标t,再用t更新其他值
**/

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510;

int n, m;

int g[N][N];

int dist[N];
bool st[N];

int dijkstra(){
    
    //初始化dist数组为最大值,说明点1到其他点都不联通
    memset(dist, 0x3f, sizeof dist);
    //点1到自己的距离为0
    dist[1] = 0;
    
    //循环n-1次
    for (int i = 0; i < n - 1; i ++ ){
        int t = -1;
        //从dist中找最小值,且没被标记过的
        for (int j = 1; j <= n; j ++ ){
            if(!st[j] && (t == -1 || dist[t] > dist[j])) t = j;
        }
        //用过的标记以下
        st[t] = true;
        //用t更新其他值
        for (int j = 1; j <= n; j ++ ){
            //从点1到点t的距离加上从点t到点j的距离
            dist[j] = min(dist[j], dist[t] + g[t][j]);
        }
    }
    //点1到点n为最大值,说明不存在从点1到点n的道路
    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

int main()
{
    //最开始点与点之间都不互通
    memset(g, 0x3f, sizeof g);
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i ++ ){
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        //若有重复,取最小值
        g[x][y] = min(g[x][y], z);
    }
    printf("%d", dijkstra());
}

堆优化版acwing850

/**
 * 从朴素版的dijkstra算法可以看到,在第二层循坏寻找dist的最小值的时候每次都要遍历整个dist数组,非常耗时
 * 根据每次需要取最小值,删除最小值且要保存所有元素来看,可以用最小堆(优先队列)来优化
 **/
 
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

typedef pair<int, int> PII;

const int N = 150010;

int n, m;
//n远小于与n的平方,是稀疏图图,用邻接表存
int e[N], ne[N], h[N], w[N], idx;
int dist[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++;
}

int dijkstra(){
    //小顶堆
    priority_queue<PII, vector<PII>, greater<PII> > q;
    memset(st, 0, sizeof st);
    //初始化dist
    memset(dist, 0x3f, sizeof dist);
    //点1到点1的距离为0
    dist[1] = 0;
    //往堆加入值,第一个是点,第二个是该点到点1的距离
    //点1到点1的距离为0
    //添加顺序不能换成{1,0},因为上面定义的堆根据PII的第一个元素进行排序
    q.push({0, 1});
    
    while(q.size() > 0){
        auto t = q.top();
        q.pop();
        //p是点,dis是点1到该点的距离
        int p = t.second, dis = t.first;
        //用过不在用
        if(st[p]) continue;
        //标记用过
        st[p] = true;
        //遍历与该点相连的其他点,更新最小值
        for (int i = h[p]; i != -1; i = ne[i]){
            int j = e[i];
            //w[i]:点i到点j的距离,dist[j]:点1到点j的距离
            if(dis + w[i] < dist[j]) {
                dist[j] = dis + w[i];
                q.push({dist[j], j});
            }
        }
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
    return dist[n];
}

int main()
{
    //初始化邻接表
    idx = 0;
    memset(h, -1, sizeof h);
    
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i ++ ){
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        add(x, y, z);
    }
    printf("%d", dijkstra());
}

Bellman-Ford

1. 从点1到点n,限制经过的边数为k
2. 可以处理负权边

acwing853

三角不等式 对于点a指向点b的边c,若distp[b] <= dist[a] + c,那么称该边满足三角不等式
若所有边都满足三角不等式,则dist数组就是最短路

//三角不等式 对于点a指向点b的边c,若distp[b] <= dist[a] + c,那么称该边满足三角不等式
//若所有边都满足三角不等式,则dist数组就是最短路
//循坏次数,循坏边数

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 510,M = 10010;

struct E{
    int a, b, c;
} e[M];

int n, m, k;
//bk数组防止用上次一循环中更新过的点更新其他点
int dist[N], bk[N];

bool bellmanFord(){
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    for (int i = 1; i <= k; i ++ ){
        memcpy(bk, dist, sizeof dist);
        for (int j = 1; j <= m; j ++ ){
            int a = e[j].a, b = e[j].b, c = e[j].c;
            dist[b] = min(dist[b], bk[a] + c);
        }
    }
    if(dist[n] <= 0x3f3f3f3f / 2) return true;
    return false;
}

int main()
{
    scanf("%d%d%d", &n, &m, &k);
    for (int i = 1; i <= m; i ++ ) scanf("%d%d%d", &e[i].a, &e[i].b, &e[i].c);
    if(bellmanFord()){
        printf("%d", dist[n]);
    }else{
        printf("%s", "impossible");
    }
}


SPFA

由三角不等式dist[b] <= dist[a] + c可知,dist会变小是因为dist[a]变小;我们可以用单调队列来优化这一过程,队列一开始只有起点1,重复以下步骤直至队列为空

  1. 取出队头保存的点a
  2. 遍历该点的所有出边
  3. 更新最小值,若有最小值更新,若该点不存在队列中,将该点加入如队列尾

acwing851

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 1e5 + 10;
int dist[N];
//判断点是否存在队列中,防止重复添加
bool st[N];
//邻接表存
int e[N], ne[N], w[N], h[N], idx;
int n, m;


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

bool spfa(){
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    queue<int> q;
    q.push(1);
    
    while (q.size() > 0){
        int a = q.front();
        q.pop();
        //取出来的a不在队列中了
        st[a] = false;
        //遍历a的所有出边
        for (int i = h[a]; i != -1; i = ne[i]){
            int b = e[i];
            //不满足三角不等式,需要更新
            if(dist[b] > dist[a] + w[i]) {
                dist[b] = dist[a] + w[i];
                //判断点b是否在队列中,若不在,加入队列
                if(!st[b]){
                    q.push(b);
                    st[b] = true; 
                } 
            }
        }
    }
    if(dist[n] != 0x3f3f3f3f) return true;
    return false;
}

int main()
{
    memset(h, -1, sizeof h);
    idx = 0;
    
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i ++ ){
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        add(x, y, z);
    }
    
    if(spfa()){
        printf("%d", dist[n]);
    }else{
        printf("%s", "impossible");
    }
}

spfa判断负环(acwing852)

在spfa的基础上再添加一个cnt数组,表示从点1到该点经过的边数,若到达某点的边数大于等于n,则存在负环;
根据抽屉原理,经过n条边=>有n+1个点,总共才有n个点,必定某个地方存在负环,导致其不满足三角不等式,使到达该点经过的边数一直在增加

问题1:为什么起始要将所有点都加入到队列里?
答:(这里引用y总的话)每次做一遍spfa()一定是正确的,但时间复杂度较高,可能会超时。初始时将所有点插入队列中可以按如下方式理解:在原图的基础上新建一个虚拟源点,从该点向其他所有点连一条权值为0的有向边。那么原图有负环等价于新图有负环。 此时在新图上做spfa,将虚拟源点加入队列中。然后进行spfa的第一次迭代,这时会将所有点的距离更新并将所有点插入队列中。 执行到这一步,就等价于视频中的做法了。那么视频中的做法可以找到负环,等价于这次spfa可以找到负环,等价于新图有负环,等价于原图有负环。得证。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 1e5 + 10;
int dist[N], cnt[N];
//判断点是否存在队列中,防止重复添加
bool st[N];
//邻接表存
int e[N], ne[N], w[N], h[N], idx;
int n, m;


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

bool spfa(){
    //不需要初始化最大值,因为某点存在负环,其一定不满足三角不等式,导致dist越更来越小
    queue<int> q;
    /**
     * 每次做一遍spfa()一定是正确的,但时间复杂度较高,可能会超时。初始时将所有点插入队列中可以按如下方式理解:
      在原图的基础上新建一个虚拟源点,从该点向其他所有点连一条权值为0的有向边。那么原图有负环等价于新图有负环。
      此时在新图上做spfa,将虚拟源点加入队列中。然后进行spfa的第一次迭代,这时会将所有点的距离更新并将所有点插入队列中。
      执行到这一步,就等价于视频中的做法了。
      那么视频中的做法可以找到负环,等价于这次spfa可以找到负环,等价于新图有负环,等价于原图有负环。得证。
     **/
    for (int i = 1; i <= n; i ++ ){
        q.push(i);
        st[i] = true;
    }
    
    while (q.size() > 0){
        int a = q.front();
        q.pop();
        //取出来的a不在队列中了
        st[a] = false;
        //遍历a的所有出边
        for (int i = h[a]; i != -1; i = ne[i]){
            int b = e[i];
            //不满足三角不等式,需要更新
            if(dist[b] > dist[a] + w[i]) {
                //到达该点经过的边数+1
                cnt[b] = cnt[a] + 1;
                //存在负环
                if(cnt[b] >= n) return true;
                dist[b] = dist[a] + w[i];
                //判断点b是否在队列中,若不在,加入队列
                if(!st[b]){
                    q.push(b);
                    st[b] = true; 
                } 
            }
        }
    }
    return false;
}

int main()
{
    memset(h, -1, sizeof h);
    idx = 0;
    
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i ++ ){
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        add(x, y, z);
    }
    
    if(spfa()){
        printf("%s", "Yes");
    }else{
        printf("%s", "No");
    }
}

多源最短路floyd

作用:求每两个点的最短路径,可以处理负权边
floyd是动态规划,
f[k][i][j]经过若干个编号不超过k的节点,从点i到点j的最短路长度。该问题可划分称两个子问题,经过编号不超过k-1的节点从i到j,或者从i先到k,再从k再到j

D [ k , i , j ] = m i n ( D [ k , i , j ] , D [ k − 1 , i , k ] , D [ k − 1 , k , j ] ) D[k, i , j] = min(D[k, i , j], D[k-1, i, k], D[k-1, k , j]) D[k,i,j]=min(D[k,i,j],D[k1,i,k],D[k1,k,j])
初始值为 D [ 0 , i , j ] = a [ i , j ] D[0, i, j] = a[i, j] D[0,i,j]=a[i,j]
k这一维可以省略
D [ i , j ] = m i n ( D [ i , j ] , D [ i , k ] + D [ k , j ] ) D[i , j] = min(D[i , j], D[i, k] + D[k , j]) D[i,j]=min(D[i,j],D[i,k]+D[k,j])
具体做法

  1. 初始化,点p到点p为0,到其他点为正无穷
  2. 循坏k,循环i,循环j

acwing854

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 210, INF = 1e9;

int dist[N][N];
int n, m, q;

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

int main(){
    scanf("%d%d%d", &n, &m, &q);
    //初始化
    for (int i = 1; i <= n; i ++ ){
        for (int j = 1; j <= n; j ++ ){
            if(i == j) dist[i][j] = 0;
            else dist[i][j] = INF;
        }
    }
    //读入数据
    for (int i = 1; i <= m; i ++ ){
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        dist[x][y] = min(dist[x][y], z);
    }
    
    floyd();
    
    for (int i = 1; i <= q; i ++ ){
        int x, y;
        scanf("%d%d", &x, &y);
        //这里是因为有负权边,只要值大于一个比较大的数即可
        if(dist[x][y] > INF / 2) printf("%s\n", "impossible");
        else printf("%d\n", dist[x][y]);
    }
}

最小生成树

给定一张边带权的无向图 G=(V,E),其中 V 表示图中点的集合,E 表示图中边的集合,n=|V|,m=|E|。
由 V 中的全部 n 个顶点和 E 中 n−1 条边构成的无向连通子图被称为 G 的一棵生成树,其中边的权值之和最小的生成树被称为无向图 G 的最小生成树。

Prim算法求最小生成树

具体做法:第一层循环n次,每次把一个点加入集合,共n个点,所以要循环n次;dist[t]的含义是集合外距离集合内最近的点的距离,第二层循环找集合外距离集合内最近的点,若t不是第一个点且t的值为正无穷,即当前距离集合最近的点是正无穷,无解;把t加入集合,更新答案;再用t更新其他点

acwing858

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510, INF = 0x3f3f3f3f;

//m与n的平方相近,用邻接矩阵
int g[N][N];
//dist是集合外距离集合内最近的点的距离
int dist[N];
//判断该点是否在集合内
bool st[N];
int n, m;

int prim(){
    //初始化为正无穷
    memset(dist, 0x3f, sizeof dist);
    int res = 0;
    //循环n次
    for (int k = 1; k <= n; k ++ ){
        //找出集合外距离集合内最近的点
        int t = -1;
        for (int j = 1; j <= n; j ++ ){
            if(!st[j] && (t == -1 || dist[t] > dist[j])) t = j;
        }
        //如果不是第一个点且是正无穷,无解
        if(t != 1 && dist[N] == INF) return INF;
        //把该点加入集合
        st[t] = true;
        //如果不是第一个点,更新答案
        if(t != 1) res += dist[t];
        //用该点更新其他点
        for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]);
    }
    return res;
}

int main(){
    
    //一开始点与点不连通
    memset(g, 0x3f, sizeof g);
    
    scanf("%d%d", &n, &m);
    for (int i = 0; i < m; i ++ ){
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        //无向图
        g[u][v] = g[v][u] = min(g[u][v], w);
    }
    int res = prim();
    //存在负权边,只要大于比较大的数就是无解
    if(res > INF / 2){
        printf("%s", "impossible");
    }else printf("%d", res);
}

Kruskal算法求最小生成树

具体做法:先根据边的权值从小达到大排序;然后依次枚举每条边,若该边的两点不连通,累加该边的权值到res,连通该两点,经过的边数cnt加1。根据生成树的定义,最后是要有n个点n-1条边连成的连通图,因此,若cnt<n-1,无解

acwing 859

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10, M = 2e5 + 10;

struct E{
    int a, b, w;
} e[M];

//并查集
int p[N];

int cmp(E e1, E e2){
    return e1.w < e2.w;
}

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

int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    
    //初始化并查集
    for (int i = 1; i <= n; i ++ ) p[i] = i;
    
    for (int i = 0; i < m; i ++ ){
        scanf("%d%d%d", &e[i].a, &e[i].b, &e[i].w);
    }
    
    //按权值从小到大排序
    sort(e, e + m, cmp);
    
    int res = 0, cnt = 0;
    //依次枚举每条边
    for (int i = 0; i < m; i ++ ){
        int a = e[i].a, b = e[i].b, w = e[i].w;
        //找到a,b的父亲
        int pa = find(a), pb = find(b);
        //不相等就是不连通
        if(pa != pb){
            res += w;
            cnt += 1;
            //将两点连通
            p[pa] = pb;
        }
    }
    
    //根据生成树的定义,经过的边数小于n-1.无解
    if(cnt < n- 1) printf("%s", "impossible");
    else printf("%d", res);
}

二分图

设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。
简而言之,就是顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属于这两个互不相交的子集,两个子集内的顶点不相邻。
在这里插入图片描述
若一个图是二分图,当且仅当不存在奇数环,奇数环是边数为奇数的环

染色法判断二分图

根据二分图的定义,一条边的两点不在同一集合且集合内部没有变相连;可以用颜色标记每个点,若一点颜色是1,那么与之相连的所有点颜色是2,若染色过程没有发生矛盾,则该图是二分图

acwing860

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10, M = 2e5 + 10;

//稀疏图,邻接表
int e[M], ne[M], h[N], 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 u, int c){
    //染色操作
    color[u] = c;
    //遍历该点的所有出边,染成相反颜色
    for (int i = h[u]; i != -1; i = ne[i]){
        int j = e[i];
        //没被染色,去染相反颜色
        if(color[j] == 0) {
            if(!dfs(j, 3 - c)) return false;
        }
        //已被染色,判断是否发生矛盾
        else {
            //相连的两个点的颜色不能相同
            if(color[j] == c) return false;
        }
    }
    return true;
}

int main(){
    
    //初始化邻接表
    memset(h, -1, sizeof h);
    idx = 0;
    
    scanf("%d%d", &n, &m);
    //无向图,需要加两次
    for (int i = 0; i < m; i ++ ){
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
        add(b, a);
    }
    //假定染色没有产生矛盾
    int flag = false;
    for (int i = 1; i <= n; i ++ ){
        //该点没被染色,去染色
        if(color[i] == 0){
            if(!dfs(i, 1)){
                //染色产生矛盾
                flag = true;
                break;
            }
        }
    }
    if(flag) printf("%s", "No");
    else printf("%s", "Yes");
}

二分图最大匹配

二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。
二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。

即二分图中有多少个边的端点没有连其他边

acwing861

具体做法:

  1. 邻接表存边, match数组存右边点集中与之对应的左边点集的点;st数组表示左边的点有没有遍历过右边的点;
  2. 枚举二分图左边的点集x,每次清空右边点集的状态,表示右边的每个点都没有考虑过;
  3. 枚举与该点相连的点t,match[t] == 0或为match[t]找到与之匹配的另外一个点,则匹配成功,match[t] =x;否则匹配失败
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 510, M = 1e5 + 10;

int match[N];
bool st[N];
int e[M], ne[M], h[N], idx;
int n1, n2, m;

void add(int a, int b)  // 添加一条边a->b
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

bool find(int x){
    for (int i = h[x]; 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] = x;
            return true;
        }
    }
    return false;
}

int main(){
    //初始化邻接表
    memset(h, -1, sizeof h);
    idx = 0;
    
    scanf("%d%d%d", &n1, &n2, &m);
    for (int i = 1; i <= m; i ++ ){
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
    }
    
    int res = 0;
    for (int i = 1; i <= n1; i ++ ){
        //重置状态数组,表示右边的点每一个都没考虑过
        memset(st, false, sizeof st);
        if(find(i)) res ++;
    }
    printf("%d", res);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值