【图论】二分图的应用(染色法判断二分图,最大匹配,最小点覆盖,最大独立集,最小路径点覆盖,最小路径重复点覆盖)

概念

什么是二分图?

顾名思义就是能分成两个部分的图
二分图是一个图,它的顶点可以分为两个独立的集合 U U U V V V,这样每一条边(u,v)(一个点在 U U U,一个点在 V V V)要么从 u u u v v v连接一个顶点,要么从 v v v u u u连接一个顶点。换句话说,对于每一条边(u,v),要么 u u u属于 U U U,要么 v v v属于 V V V,或者 u u u属于 V V V v v v属于 U U U,也可以说没有边连接同一集合的不同点

二分图的判定

二分图<=>不存在奇数环<=>染色法不存在矛盾

  • 只要染色法不存在矛盾,那么假定黑白染色,染色为黑色的放在一个集合,染色为白色的放在一个集合,这显然是一个二分图。
  • 所以可以用染色法来判定是否是二分图,如果可以通过两种颜色将图染色,使得相同集合中的顶点使用相同的颜色,则为二分图。无法使用两种颜色为奇数顶点个数的图染色。
    如下图所示,这是个奇数环,那么免不了有两个染色相同的点会相连
    在这里插入图片描述

匹配问题

名词概念

  • 匹配: 设G=<V, E>为二分图,如果 M⊆E,并且 M 中两点没有任何两点有公共端点(被其他匹配的边共用),则成M为G的一个匹配。【也就是说匹配的实质是一些边的集合。】图三图四,就是图二的匹配
    在这里插入图片描述
    我们定义匹配点、匹配边、未匹配点、非匹配边,它们的含义非常显然。例如图 3 中 1、4、5、7 为匹配点,其他顶点为未匹配点;1-5、4-7为匹配边,其他边为非匹配边。
  • 最大匹配: 边数最多的匹配,图四就是一个最大匹配
  • 最小点覆盖: 用尽可能少的点去覆盖所有的边【最小点覆盖集是点的集合,其个数为最小点覆盖数】
  • 最大独立集: 选出最多的点,使得选出的点之间没有边
  • 最大团: 选出最多的点,使得选出的点之间都有边
  • 最小路径点覆盖 :针对一个DAG(有向无环图),用最少的,互不相交的路径(点不重复),将所有点覆盖住。
  • 增广路径: 从一个非匹配点走,先走非匹配边,再走匹配边,再走非匹配边…最后走到一个非匹配点。图 5 中的一条增广路如图 6 所示(图中的匹配点均用红色标出)。最大匹配等价于不存在增广路径
    在这里插入图片描述
    在这里插入图片描述
    增广路有一个重要特点:非匹配边比匹配边多一条。只要把增广路中的匹配边和非匹配边的身份交换即可。由于中间的匹配节点不存在其他相连的匹配边,所以这样做不会破坏匹配的性质。交换后,图中的匹配边数目比原来多了 1 条。 我们可以通过不停地找增广路来增加匹配中的匹配边和匹配点。找不到增广路时,达到最大匹配(这是增广路定理)。匈牙利算法正是这么做的。

匈牙利算法

匈牙利算法的讲解博客有很多人讲的很详细了,我这里不再阐述。匈牙利算法
最大匹配数=最小点覆盖=总点数-最大独立集=总点数-最小路径点覆盖
证明在下面的对应的题目中

染色法判断二分图-关押罪犯

S S S城现有两座监狱,一共关押着 N N N 名罪犯,编号分别为 1 ∼ N 1∼N 1N
他们之间的关系自然也极不和谐。
很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。
我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。
如果两名怨气值为 c c c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为 c c c 的冲突事件。
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到 S S S Z Z Z 市长那里。
公务繁忙的 Z Z Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。
在详细考察了 N N N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。
他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。
假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。
那么,应如何分配罪犯,才能使 Z Z Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?

输入格式
第一行为两个正整数 N N N M M M,分别表示罪犯的数目以及存在仇恨的罪犯对数。
接下来的 M M M 行每行为三个正整数 a j aj aj b j bj bj c j cj cj,表示 a j aj aj 号和 b j bj bj 号罪犯之间存在仇恨,其怨气值为 c j cj cj
数据保证 1 ≤ a j < b j < N 1≤aj<bj<N 1aj<bj<N, 0 < c j ≤ 1 0 9 0<cj≤10^9 0<cj109 且每对罪犯组合只出现一次。

输出格式
输出共 1 1 1 行,为 Z Z Z 市长看到的那个冲突事件的影响力。
如果本年内监狱中未发生任何冲突事件,请输出 0 0 0

数据范围
N ≤ 20000 N≤20000 N20000, M ≤ 100000 M≤100000 M100000
输入样例:

4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884

输出样例:

3512

分析: 二分,染色法判断二分图 O ( ( N + M ) l o g C ) O((N+M)logC) O((N+M)logC)
将罪犯当做点,罪犯之间的仇恨关系当做点与点之间的无向边,边的权重是罪犯之间的仇恨值。那么原问题变成:将所有点分成两组,使得各组内边的权重的最大值尽可能小
看到最大值最小,我们就会想到二分了。
我们在 [ 0 , 1 0 9 ] [0,10^9] [0,109] 之间枚举最大边权 l i m i t limit limit,当 l i m i t limit limit 固定之后,剩下的问题就是:
能否将所有点分成两组,使得所有大于边权 l i m i t limit limit的边都在组间,而不在两组之内。所以这就是一个二分图的问题。判定所有大于边权limit的边所构成的新图是不是二分图
判断二分图可以用染色法。为了加速算法,我们来考虑是否可以用二分枚举 l i m i t limit limit,在用大于边权 l i m i t limit limit的边构成二分图之后,假定最终组内最大边权的最小值是 A n s Ans Ans:

  • 那么当 l i m i t ∈ [ a n s , 1 0 9 ] limit∈[ans,10^9] limit[ans,109] 时,所有边权大于 l i m i t limit limit 的边,必然是所有边权大于 A n s Ans Ans 的边的子集,肯定都分配在了组间,因此由此构成的新图也是二分图。
  • l i m i t ∈ [ 0 , a n s − 1 ] limit∈[0,ans−1] limit[0,ans1]时,由于 a n s ans ans 是新图可以构成二分图组内的最大边权的最小值,因此由大于 l i m i t limit limit 的边构成的新图一定不是二分图。
    所以整个区间具有二段性,可以二分出分界点 a n s ans ans 的值。

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=200005,M=200005;
int n,m;
int e[M],w[M],ne[M],h[N],tot;
int color[N];//0表示未染色,1表示染白色,2表示染黑色
void add(int a,int b,int c){
    e[tot]=b,w[tot]=c,ne[tot]=h[a],h[a]=tot++;
}
bool dfs(int u,int c,int mid){
    color[u]=c;
    for(int i=h[u];~i;i=ne[i]){
        int v=e[i];
        if(w[i]<=mid)continue;
        if(color[v]){
            if(color[v]==c)return false;
        }
        else if(!dfs(v,3-c,mid))return false;
    }
    return true;
}
bool check(int mid){
    memset(color,0,sizeof(color));
    for(int i=1;i<=n;i++){
        if(!color[i]){//开始染色
            if(!dfs(i,1,mid))return false;
        }
    }
    return true;
}
int main()
{
    scanf("%d %d",&n,&m);
    memset(h,-1,sizeof(h));
    for(int i=1;i<=m;i++){
        int a,b,c;scanf("%d %d %d",&a,&b,&c);
        add(a,b,c),add(b,a,c);
    }
    int l=0,r=1e9;
    while(l<r){
        int mid=l+r>>1;
        if(check(mid))r=mid;
        else l=mid+1;
    }
    printf("%d\n",r);
    return 0;
}

最大匹配-棋盘覆盖

给定一个 N N N N N N 列的棋盘,已知某些格子禁止放置。

求最多能往棋盘上放多少块的长度为 2、宽度为 1 的骨牌,骨牌的边界与格线重合(骨牌占用两个格子),并且任意两张骨牌都不重叠。

输入格式
第一行包含两个整数 N N N t t t,其中 t t t 为禁止放置的格子的数量。

接下来 t t t 行每行包含两个整数 x x x y y y,表示位于第 x x x 行第 y y y 列的格子禁止放置,行列数从 1 开始。

输出格式
输出一个整数,表示结果。

数据范围
1 ≤ N ≤ 100 1≤N≤100 1N100,
0 ≤ t ≤ 100 0≤t≤100 0t100

输入样例:
8 0

输出样例:
32

分析: 这个题乍一看和二分图没什么关系
在这里插入图片描述
仔细分析,题目问最多能放多少个骨牌,使得任意两个骨牌都不重叠,意思就是能够取多少条边,使得所有选出来的边没有公共点。实际上就是找一个最大匹配。那么这个图是不是二分图呢?只有是二分图我们才能用匈牙利算法,二分图等价于能够二染色。
我们将所有的格子分成奇数格和偶数格,如下图所示,绿格和白格直接是有边的。绿格和绿格,白格和白格之间是没有边的。我们将所有的白格放一边,绿格放一边,所有的边必然是在两个集合之间的。所以这个图是二分图。
在这里插入图片描述
编号之后,偶数点是绿点,奇数点是白点
在这里插入图片描述
这个问题的难点就是如何转化为最大匹配问题

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=105;
typedef pair<int,int>PII;
int n,t;
bool mp[105][105];
bool vis[105][105];
PII match[N][N];
//匈牙利算法   二分图最大匹配
int d[4][2]={1,0,-1,0,0,1,0,-1};
bool check(int x,int y){
    if(x<1||y<1||x>n||y>n)return 0;
    if(vis[x][y]||mp[x][y])return 0;//在当前次已经匹配过了或者不允许放在这个位置
    return 1;
}
bool Find(int x,int y){
    for(int i=0;i<4;i++){
        int dx=x+d[i][0],dy=y+d[i][1];
        if(!check(dx,dy))continue;
        vis[dx][dy]=1;
        PII t=match[dx][dy];
        if(t.first==0||Find(t.first,t.second)){//当前位置还没有匹配或者当前位置可以换成别的匹配
            match[dx][dy]={x,y};
            return true;
        }
    }
    return false;
}
int main()
{
    scanf("%d %d",&n,&t);
    while(t--){
        int a,b;scanf("%d %d",&a,&b);
        mp[a][b]=1;
    }
    int res=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
        //随便枚举奇数点或者偶数点
            if((i+j)%2&&!mp[i][j]){
                memset(vis,0,sizeof(vis));
                if(Find(i,j))res++;//有连接的点
            }
        }
    }
    printf("%d\n",res);
}

最小点覆盖-机器任务

有两台机器 A A A B B B 以及 K K K 个任务。
机器 A A A N N N 种不同的模式(模式 0 ∼ N − 1 0∼N−1 0N1),机器 B B B M M M 种不同的模式(模式 0 ∼ M − 1 0∼M−1 0M1)。
两台机器最开始都处于模式 0 0 0
每个任务既可以在 A A A 上执行,也可以在 B B B 上执行。
对于每个任务 i i i,给定两个整数 a [ i ] a[i] a[i] b [ i ] b[i] b[i],表示如果该任务在 A A A 上执行,需要设置模式为 a [ i ] a[i] a[i],如果在 B B B 上执行,需要模式为 b [ i ] b[i] b[i]
任务可以以任意顺序被执行,但每台机器转换一次模式就要重启一次。
求怎样分配任务并合理安排顺序,能使机器重启次数最少。

输入格式
输入包含多组测试数据。
每组数据第一行包含三个整数 N N N, M M M, K K K
接下来 K K K 行,每行三个整数 i i i, a [ i ] a[i] a[i] b [ i ] b[i] b[i] i i i 为任务编号,从 0 0 0 开始。
当输入一行为 0 0 0 时,表示输入终止。

输出格式
每组数据输出一个整数,表示所需的机器最少重启次数,每个结果占一行。

数据范围
N , M < 100 , K < 1000 N,M<100,K<1000 N,M<100,K<1000
0 ≤ a [ i ] < N 0≤a[i]<N 0a[i]<N
0 ≤ b [ i ] < M 0≤b[i]<M 0b[i]<M

输入样例:

5 5 10
0 1 1
1 1 2
2 1 3
3 1 4
4 2 1
5 2 2
6 2 3
7 2 4
8 3 3
9 4 3
0

输出样例:

3

分析:
最小点覆盖: 用尽可能少的点去覆盖所有的边【最小点覆盖集是点的集合,其个数为最小点覆盖数】
先证明一下最小点覆盖=最大匹配数
证明:
1.最大匹配数 ≤ \leq 最小点覆盖
因为每一条匹配边,两两之间是没有交点的,所以有多少个匹配边,就必须要选多少个点出来,所以得证
2.证明等号可以成立

  1. 先求最大匹配
  2. 从左部的每个非匹配点出发,做一遍增广,标记所有经过的点
    左部所有未被标记的点和右边所有被标记的点加起来就是一个最小点覆盖的构造方案
    在这里插入图片描述
    红线代表匹配边,蓝线代表非匹配边,绿2,绿4是非匹配点,从这两个点除法,可以标记所有图中绿色的点,那么左边的未被标记的点和右边被标记的点就是一个最小点覆盖
  • 左边所有非匹配点都是标记点
  • 右边所有非匹配点一定没有被标记,如果右边某一个非匹配点被标记了,那么第一个点是非匹配点,最后一个点也是非匹配点,就多了一条增广路,那就不是最大匹配了,矛盾
  • 对于匹配边,左右两点要么同时被标记,要么同时不被标记(因为在寻找增广路的过程中,左部匹配点只能通过右部到达)

左边所有未被标记的点肯定是匹配点,右边所有被标记的点也是匹配点。所以我们选出来的点必然是在某一个匹配边的两边的。所以所有的匹配边,我们这样选择是都被覆盖了。
不在匹配中的边

  • 左边非匹配点->右边匹配点,右边匹配点一定会被标记,已经被覆盖了
  • 左边匹配点->右边非匹配点,如果标记了左边匹配点,那么右边非匹配点是一定会被标记的,按照我们的这种标记方法,所以左边的匹配点连接右边非匹配点时是不会被标记的。
  • 左边非匹配点->右边非匹配点,如果有这样的边存在,那么就多了一条增广路径,不符。
    所以我们可以构造出来匹配边就是最小点覆盖

本题目,两台机器最开始都处于模式 0 0 0。所以 a [ i ] a[i] a[i] b [ i ] b[i] b[i]等于0的任务可以不用任何代价就执行,每个任务既可以在A上执行,也可以在B上执行,那么我们就可以在 a [ i ] a[i] a[i] b [ i ] b[i] b[i]之间连一条边,只要选择每一条边的任意一点就可以了,所以问题变成,我们在所有的点中最少可以选择多少个点,可以将所有的边覆盖住,这就是一个最小点覆盖数。

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
//最小点覆盖 等价于  最大匹配
const int N=105,M=105;
int n,m,k;
bool mp[N][N];
int match[N];
int vis[M];
bool Find(int x){
    for(int i=1;i<m;i++){
        if(!vis[i]&&mp[x][i]){
            vis[i]=1;
            int t=match[i];
            if(!t||Find(t)){
                match[i]=x;
                return true;
            }
        }
    }
    return false;
}
int main(){
    while(~scanf("%d",&n)){
        if(n==0)break;
        memset(mp,0,sizeof(mp));
        memset(match,0,sizeof(match));
        scanf("%d %d",&m,&k);
        while(k--){
            int i,a,b;scanf("%d %d %d",&i,&a,&b);
            if(a==0||b==0)continue;
            mp[a][b]=1;
        }
        int res=0;
        for(int i=1;i<n;i++){//枚举左边的a
            memset(vis,0,sizeof(vis));
            if(Find(i))res++;
        }
        printf("%d\n",res);
    }
    return 0;
}

最大独立集-骑士放置

给定一个 N × M N×M N×M 的棋盘,有一些格子禁止放棋子。
问棋盘上最多能放多少个不能互相攻击的骑士(国际象棋的“骑士”,类似于中国象棋的“马”,按照“日”字攻击,但没有中国象棋“别马腿”的规则)。

输入格式
第一行包含三个整数 N , M , T N,M,T N,M,T,其中 T T T 表示禁止放置的格子的数量。

接下来 T 行每行包含两个整数 x x x y y y,表示位于第 x x x 行第 y y y 列的格子禁止放置,行列数从 1 1 1 开始。

输出格式
输出一个整数表示结果。

数据范围
1 ≤ N , M ≤ 100 1≤N,M≤100 1N,M100

输入样例:

2 3 0

输出样例:

4

分析:
最大独立集: 选出最多的点,使得选出的点之间没有边
最大独立集等价于去掉最少的点使得所有的边都破坏掉等价于找最小点覆盖等价于最大匹配
本题目,每一个棋子可以攻击到的点
在这里插入图片描述
和前面的棋盘覆盖的题类似。如果两个点之间可以相互攻击到,我们在这两个点之间连一条边。本题意思就是找出最多的点,使得选出的所有点之间两两是不能攻击到的,也就是选出的所有点之间都是没有边的,这就是一个最大独立集的问题。只有二分图才能使用最大匹配的算法。那么这个图是不是二分图呢。
在这里插入图片描述
我们将棋盘染色,横纵坐标之和为奇数的点染色为白色,偶数染色为蓝色,可以看到,所有互相连接的点都是蓝色和白色。所以可以二染色,就是二分图。
从数学上看,坐标为 ( x , y ) (x,y) (x,y)的点,每一次攻击的位置都是一边 + 2 +2 +2或者 − 2 -2 2,另一边 + 1 +1 +1或者 − 1 -1 1(x,y)的攻击位置 ( x , y ) (x,y) (x,y)的关系都是加或者减一个奇数的,攻击的位置横纵坐标之和和 ( x , y ) (x,y) (x,y)坐标和的奇偶性都是不一样的,所以两者肯定是不一样的颜色,所以是二分图。
本题最后答案就是总点数-最大匹配数量(最小点覆盖数量)-禁止放棋子的数量

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
//最大独立集   总的点数减去最小点覆盖的点数减去无用点数
const int N=105,M=105;
typedef pair<int,int>PII;
int n,m,t;
bool mp[N][N];
PII match[N][N];
bool vis[N][N];
int d[8][2]={1,2,1,-2,-1,2,-1,-2,2,1,2,-1,-2,1,-2,-1};
bool Find(int x,int y){
    for(int i=0;i<8;i++){
        int dx=x+d[i][0],dy=y+d[i][1];
        if(vis[dx][dy]||mp[dx][dy])continue;
        if(dx<1||dy<1||dx>n||dy>m)continue;
        vis[dx][dy]=1;
        PII t=match[dx][dy];
        if(t.first==0||Find(t.first,t.second)){
            match[dx][dy]={x,y};
            return true;
        }
    }
    return false;
}
int main()
{
    scanf("%d %d %d",&n,&m,&t);
    for(int i=1;i<=t;i++){
        int a,b;
        scanf("%d %d",&a,&b);
        mp[a][b]=1;
    }
    int res=0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if((i+j)%2||mp[i][j])continue;
            memset(vis,0,sizeof(vis));
            if(Find(i,j))res++;
        }
    }
    printf("%d\n",n*m-res-t);
    return 0;
}

最小路径重复点覆盖-捉迷藏

V a n i Vani Vani c l 2 cl2 cl2 在一片树林里捉迷藏。
这片树林里有 N N N 座房子, M M M 条有向道路,组成了一张有向无环图。
树林里的树非常茂密,足以遮挡视线,但是沿着道路望去,却是视野开阔。
如果从房子 A A A 沿着路走下去能够到达 B B B,那么在 A A A B B B 里的人是能够相互望见的。
现在 c l 2 cl2 cl2 要在这 N N N 座房子里选择 K K K 座作为藏身点,同时 V a n i Vani Vani 也专挑 c l 2 cl2 cl2 作为藏身点的房子进去寻找,为了避免被 V a n i Vani Vani 看见, c l 2 cl2 cl2 要求这 K K K 个藏身点的任意两个之间都没有路径相连。
为了让 V a n i Vani Vani 更难找到自己, c l 2 cl2 cl2 想知道最多能选出多少个藏身点。

输入格式
输入数据的第一行是两个整数 N N N M M M
接下来 M M M 行,每行两个整数 x , y x,y x,y,表示一条从 x x x y y y 的有向道路。

输出格式
输出一个整数,表示最多能选取的藏身点个数。

数据范围
N ≤ 200 , M ≤ 30000 N≤200,M≤30000 N200,M30000

输入样例:

7 5
1 2
3 2
2 4
4 5
4 6

输出样例:

3

分析:
最小路径点覆盖 :针对一个DAG(有向无环图),用最少的,互不相交的路径(点不重复),将所有点覆盖住。
拆点,将每个点拆成出点和入点
在这里插入图片描述
如图将1->2->3可以拆成这样的两条边,对于原图的互不相交的路径,路径上的每一个点最多只有一个出度和一个入度,所以将原图的所有互不相交的路径转化到新图中,因为每个点最多只属于一条边,一定对应一个匹配
而所有的路径的终点,都对应左侧的一个非匹配点,就像上图,3是该路径的终点,3是一个左边的非匹配点,因为它没有出边了。
所以我们要求原图的最少的互不相交的路径的数量等价于求新图的左侧的最少的非匹配点的数量等价于让左侧的匹配点m最多,就是找最大匹配。最后的答案就是n-m

最小路径重复点覆盖
点可以重复
1.先求传递闭包 G‘
2.原图G的最小路径重复点覆盖就是在求完传递闭包的新图G’的最小路径点覆盖
证明:
对于图G的一种覆盖方式
在这里插入图片描述
比如这条边,有两个重复的点 A A A,那么我们在 G ‘ G‘ G中的覆盖方式就是跳过 A A A选择其他的点,不选择重复的点,因为 G ’ G’ G G G G的传递闭包,所以任意一条边上两点都是相连的
对于左边的某一个选择路径,不可能存在全部的点都被覆盖过了,因为这样有这个路径没这个路径都是一样的。
而对于 G ‘ G‘ G的每一条边,我们都可以通过展开 G ’ G’ G来还原成 G G G,二者是等价的。
所以原图 G G G最小路径重复点覆盖就是在求完传递闭包的新图 G ’ G’ G最小路径点覆盖的集合是等价的

本题目,给定一个有向无环图,从中选出最多的点,使得每两个点都不能从其中一个走到另外一个。假设最小路径重复点覆盖有cnt条路,那么每条路上只能选一个点,如果选了两个点,是 c l 2 cl2 cl2是会被 V a n i Vani Vani看到的,所以答案现在证明是 ≤ \leq cnt。
如果我们构造出方案答案≥cnt,就可以证明最后的答案其实就是cnt
现在的最小路径重复点覆盖有cnt条,我们将每一条路的终点放进一个集合E中,所有终点可以到的点记作next(E)。

  • E ∩ n e x t ( E ) = ϕ E \cap next(E)=\phi Enext(E)=ϕ,此时E里面就有cnt个点,那么E里面的点就是每种方案。
  • E ∩ n e x t ( E ) ≠ ϕ E \cap next(E)≠\phi Enext(E)=ϕ,对于任何一个 e i e_i ei来说,每次拿出某一个 e i e_i ei,让 e i e_i ei一直往前走,走到 e i e_i ei不属于 n e x t ( E ) next(E) next(E)为止,对于哪条路,最远也只能走到起点,因为如果一直到起点都属于 n e x t ( E ) next(E) next(E),那么这条路就可以归为另一条路的后面了。

所以本题其实就是求最小路径重复点覆盖的路径的条数。

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
//最小路径点覆盖   用最少的可以相交的边,将所有点覆盖
const int N=205,M=30005;
int n,m;
bool mp[N][N];
bool vis[N];
int match[N];
bool Find(int x){
    for(int i=1;i<=n;i++){
        if(!vis[i]&&mp[x][i]){
            vis[i]=1;
            int t=match[i];
            if(!t||Find(t)){
                match[i]=x;
                return true;
            }
        }
    }
    return false;
}
int main()
{
    scanf("%d %d",&n,&m);
    while(m--){
        int a,b;
        scanf("%d %d",&a,&b);
        mp[a][b]=1;
    }
    //传递闭包
    for(int k=1;k<=n;k++){
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                mp[i][j]|=mp[i][k]&mp[k][j];
            }
        }
    }
    int res=0;
    for(int i=1;i<=n;i++){
        memset(vis,0,sizeof(vis));
        if(Find(i))res++;
    }
    printf("%d\n",n-res);
    return 0;
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

a碟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值