最大匹配数 = 最小点覆盖 = 总点数-最大独立集 = 总点数-最小路径覆盖
最大匹配数:二分图两部分点集之间彼此最多能连多少条边
最小点覆盖:选出最少的m个点,使得m个点的关联边包含原图的所有边
最大独立集:选出最多的m个点,使这m个点两两之间没有边相关联
最小路径覆盖:在有向无环图中选出最少的路径,路径之间不能有重合点,覆盖所有点
染色法判断二分图(不存在长度为奇数的环)
#include<bits/stdc++.h>
#define int long long
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const int maxn=2e5+5;
const int inf=1e9+7;
const int mod=1e9+7;
int color[maxn];
vector<int>vec[maxn];
int dfs(int u,int c,int fa){
color[u]=c;
for(int v:vec[u]){
if(v==fa){
continue;
}
else{
if(color[v]){
if(color[u]==color[v]){
return 0;
}
}
else{
if(!dfs(v,3-c,u)){
return 0;
}
}
}
}
return 1;
}
void solve(){
int n,m;
cin>>n>>m;
while(m--){
int a,b;
cin>>a>>b;
vec[a].push_back(b);
vec[b].push_back(a);
}
int check=1;
for(int i=1;i<=n;i++){
if(!color[i]){
if(!dfs(i,1,0)){
check=0;
break;
}
}
}
if(check){
cout<<"Yes"<<"\n";
}
else{
cout<<"No"<<"\n";
}
}
signed main(){
int t=1;
//cin>>t;
while(t--){
solve();
}
}
/*
4 4
1 3
1 4
2 3
2 4
Yes
*/
匈牙利算法 求二分图最大匹配
#include<bits/stdc++.h>
using namespace std;
int n1,n2,m;
const int maxn=5e2+5;
int vis[maxn],match[maxn],a[maxn][maxn];
int path(int u){
for(int i=1;i<=n2;i++){ //1-n2是否有位置能跟u匹配
if(!vis[i]&&a[u][i]){ //让过位置没有,没有的话试试让位置
vis[i]=1;
if(!match[i]||path(match[i])){ //让match[i]试试能不能找到新的位置
match[i]=u;
return 1;
}
}
}
return 0;
}
int main(){
cin>>n1>>n2>>m;
memset(match,0,sizeof(match));
while(m--){
int x,y;
cin>>x>>y;
a[x][y]=1;
}
int cnt=0;
for(int i=1;i<=n1;i++){
memset(vis,0,sizeof(vis));//代表所有的i都可以试着让位置
if(path(i)){ //给i匹配位置
cnt++;
}
}
cout<<cnt<<"\n";
}
这题把横纵坐标和为奇数的点以及横纵坐标和为偶数的点作为二分图的两个点集进行匹配,实际上也是的,因为骨牌覆盖的点就是一个横纵坐标和为偶数,一个横纵坐标和为奇数
#include <cstring>
#include <iostream>
#include <algorithm>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 110;
int n, m;
PII match[N][N];
bool g[N][N], st[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
// dfs
bool find(int x, int y)
{
for (int i = 0; i < 4; i ++ )//枚举邻点
{
int a = x + dx[i], b = y + dy[i];
if (a && a <= n && b && b <= n && !g[a][b] && !st[a][b])//不是坏点 没遍历过
{
// 则男[x,y] 和 女[a,b]能够配对
st[a][b] = true;
PII t = match[a][b];//
//1 t.x==-1说明女[a,b]还没和其他人配对 则男[x,y]和女[a,b]可以直接配对
//2 女[a,b]已经有人配对,但和女[a,b]配对的男t还有其他选项
// 男t放弃和女[a,b]配对 让女[a,b]给男[x,y]配对(我感动了)
if (t.x == -1 || find(t.x, t.y))
{
match[a][b] = {x, y};
return true;
}
}
}
return false;
}
int main()
{
cin >> n >> m;
while(m--)
{
int x,y;
cin >> x >> y;
g[x][y] = true;
}
memset(match,-1,sizeof match);
int res = 0;
// 枚举所有和为奇数的点
for(int i=1;i<=n;i++)
{
for(int j = 1;j<=n;j++)
{
if((i+j)%2 && !g[i][j])
{
memset(st,0,sizeof st);//每次都需要清空st数组,因为匹配好的一对可能会有下家
if(find(i,j))res++;//如果[i,j]能配对
}
}
}
cout << res << endl;
return 0;
}
最小点覆盖
在二分图中,最小点覆盖(选择最少的点使得这些点的关联边覆盖所有关键点)就等于最大匹配的边数
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 105, M = 2e3 + 5;
struct E{int v, next;} e[M];
int n, m, k, no, u, v, len, h[N], mat[N];
bool vis[N];
void add(int u, int v) {e[++len].v = v; e[len].next = h[u]; h[u] = len;}
bool dfs(int u) {
for (int j = h[u]; j; j = e[j].next) {
int v = e[j].v;
if (vis[v]) continue; vis[v] = true;
if (!mat[v] || dfs(mat[v])) {
mat[v] = u; return true;
}
}
return false;
}
int main() {
while (scanf("%d", &n), n) {
scanf("%d%d", &m, &k);
memset(h, 0, sizeof(h)); len = 0;
memset(mat, 0, sizeof(mat));
for (int i = 1; i <= k; i++) {
scanf("%d%d%d", &no, &u, &v);
if (!u || !v) continue;
add(u, v);
}
int ans = 0;
for (int i = 1; i < n; i++) {
memset(vis, false, sizeof(vis));
if (dfs(i)) ans++;
}
printf("%d\n", ans);
}
return 0;
}
最大独立集
选出最多的点 使得选出的点之间没有边
在二分图中 总共n个点
求最大独立集n-m(越大越好)则去掉的点数m越小越好
<=> 去掉最少的点(m个),将所有边都破坏掉
<=> 找最小点覆盖所有m条边
<=> 找最大匹配m
#include<iostream>
#include<cstring>
#include<algorithm>
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N=110;
int n,m,k;
PII match[N][N];
bool g[N][N],st[N][N];
int dirs[8][2] = {{-2,-1},{-2,1},{-1,2},{1,2},{2,1},{2,-1},{1,-2},{-1,-2}};
bool find(int x,int y)
{
for(int i = 0;i<8;i++)
{
int nx = x+dirs[i][0],ny = y+dirs[i][1];
if(nx<1 || nx>n || ny<1 || ny>m || g[nx][ny] || st[nx][ny]) continue;
st[nx][ny] = true;//男[x,y] 找到女[nx,ny]
PII t = match[nx][ny];//t为女[nx,ny]现在匹配的对象
if(t.x==0||find(t.x,t.y))//如果女[nx,ny]没有匹配对象或者现配t可以去找其他妹子 那就把[nx,ny]给[x,y]
{
match[nx][ny] = {x,y};
return true;
}
}
return false;
}
int main()
{
cin >> n >> m >> k;
for(int i = 0;i<k;i++)
{
int x,y;
cin >> x >> y;
g[x][y] = true;
}
int res =0;
for(int i =1;i<=n;i++)
{
for(int j = 1;j<=m;j++)
{
if(g[i][j] || (i+j)%2)continue;
memset(st,0,sizeof st);
if(find(i,j))res++;
}
}
cout << n*m - k - res << endl;
return 0;
}
最小路径覆盖和最小重复路径覆盖(特指在有向无环图中)
最小路径覆盖
DAG(有向无环图)
用最少的互不相交的路径 将所有点覆盖
1
2
...
n
拆成两个点
1 1'
2 2'
... ...
n n'
1→2→3
1 1'
↘
2 2'
↘
3 3'
... ...
n n'
出点 入点
原图有边i→j
则有 i→j'
出点 入点
原图变为从左边(出点)连向右边(入点)的二分图
则最少互不相交的路径=n-m(最大点覆盖数量)
原图中的每条路径 转化到新图中
每个点最多只有一个出度一个入度
<=> 新图中的任意两条边之间不相交
<=> 新图中的边都是匹配边
每个路径终点 对应 一个左侧非匹配点(3作为终点 在新图中没出边)
<=> 让左侧非匹配点最少 n-m
<=> 让左侧匹配点最多 m
<=> 找最大匹配边数 m
/*
扩展:取消对路径互不相交的约束后
问题-最小路径重复点覆盖
1 先求传递闭包
<=>
o→o→o => o→o→o
↘↗
G G'
2 原图G最小路径重复点覆盖==新图G'最小路径覆盖
证:
原图G中多条路径点重复(例如第一条路径和第二条路径都经过A) 把重复点跳过
→
↑ ↓
o→o→o→o→o→o
↓ A ↑ A
→
对第3到第n条边
如果当前边中点在前面出现过 则跳过
做完后 原图G转化为新图G'
新图G'转化为原图G(直接把外跳边取消)
→
↑ ↓
o→o→o→o→o→o
↓ A ↑ A
→
*/
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 210, M = 30010;
int n, m;
bool d[N][N], st[N];
int match[N];
bool find(int x)
{
for(int i = 1;i<=n;i++)
{
if(d[x][i] && !st[i])//x能到i 且i没被用
{
st[i] = true;
int t = match[i];
if(t==0 || find(t))
{
match[i] =x;
return true;
}
}
}
return false;
}
int main()
{
cin >> n >> m;
while(m--)
{
int a,b;
cin >> a >> b;
d[a][b] = true;
}
//传递闭包
// i k j i k j
// o→o→o => o→o→o
// ↘↗
// G G'
for(int k = 1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j = 1;j<=n;j++)
{
d[i][j] |=d[i][k] & d[k][j];
}
}
}
int res = 0;
for(int i = 1;i<=n;i++)
{
memset(st,0,sizeof st);
if(find(i))res++;
}
cout << n-res << endl;
return 0;
}
KM算法
我们假定为一些客人,为一些商品对于一些客人,客人们对商品有一定的期望值,你需要让客人买一些商品,使得他们的期望值最大(不考虑经济问题)
那么这些客人在进行商品分配的时候难免会有一些争端,于是我们可以找到一条增广路,增广路上一定有奇数个节点,并且必定是客人点多出来一个。因此,我们需要对客人和商品的标杆进行些许的调整,使得二分图中能加进来一些新的点满足客户的需求,解决他们的纠纷。
我们设lx,为客人的标杆,ly为商品的标杆。
客人的标杆必然是他们的期望值,那么商品的标杆呢,肯定就是它们的期望增高值
因此,lx的初值就是某客户对所有商品的最大期望值,ly的初值为零。
当当前标杆找不到完备匹配时,我们就要让多出来客户能买其他未被选中的商品,所以令d=min{lx[i]+ly[j]−w[i][j]}(i为增广路上客户点,j为不在增广路上的商品点)i,j的位置很好理解,肯定是有争执的客人找没有争执的商品
那么d就是我要降低的值,于是我给增广路上所有的客人的最大期望值全部降低d,但是我们不能减多了,每次整体只能减d,所以我们要将增广路上的所有商品期望都加上d,这样就能保证整体的稳定,并且能够加进来新的商品,以满足客户的需求。
#include<bits/stdc++.h>
#define inf 0x7f7f7f7f
using namespace std;
typedef long long ll;
typedef unsigned int ui;
typedef unsigned long long ull;
const int N=505;
int path[N+10],lx[N+10],ly[N+10];
int mp[N+10][N+10];
int visx[N+10],visy[N+10];
int n,m,k,ans;
int check(int x){
visx[x]=1;
for(int y=1;y<=m;y++){
if(!visy[y]&&lx[x]+ly[y]==mp[x][y]){
visy[y]=1;
}
if(path[y]<0||check(path[y])){
path[y]=x;
return 1;
}
}
return 0;
}
int km(){
int sum=0;
memset(path,-1,sizeof(path));
for(int i=1;i<=n;i++){
while(1){
memset(visx,0,sizeof(visx));
memset(visy,0,sizeof(visy));
if(check(i)) break;
int d=inf;
for(int i=1;i<=n;i++){
if(visx[i]){
for(int j=1;j<=m;j++){
if(!visy[j]){
d=min(d,lx[i]+ly[i]-mp[i][j]);
}
}
}
}
for(int i=1;i<=n;i++){
if(visx[i]){
lx[i]-=d;
}
}
for(int i=1;i<=m;i++){
if(visy[i]){
ly[i]+=d;
}
}
}
}
for(int i=1;i<=m;i++){
if(path[i]!=-1) sum+=mp[path[i]][i];
}
return sum;
}
int main(){
cin>>n>>k;
m=n;
for(int i=1;i<=n;i++){
lx[i]=-1*inf;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
mp[i][j]=-1*inf;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>mp[i][j];
lx[i]=max(lx[i],mp[i][j]);
}
}
cout<<km()<<"\n";
}