路过发现有bug的地方,还请大神指出。 (ˇˍˇ) 想~
术语:最大匹配、最小路径覆盖、最小点覆盖、最大独立集、最大团、最大(小)权值匹配。
性质:
(1) 最小路径覆盖 = 节点数 - 最大匹配。
(2) 最小点覆盖 = 最大匹配。
(3) 最大独立集 = 节点数 - 最小点覆盖 = 节点数 - 最大匹配 = 补图的最大团。
(4) 最大团 = 补图的最大独立集。
(5) 对二分图染黑、白色,要求相邻节点颜色不同,问最多可以把多少个节点染成黑色 -> 求解最大独立集。
求解最大匹配算法有三个:匈牙利、HK、KM(大材小用了)
匈牙利算法:时间复杂度O(NM)
#include
#include
#include
#include
#define MAXN (100+10)
using namespace std;
int N, M;
vector G[MAXN];
bool used[MAXN];
int match[MAXN];
int DFS(int u)
{
for(int i = 0; i < G[u].size(); i++)
{
int v = G[u][i];
if(!used[v])
{
used[v] = true;
if(match[v] == -1 || DFS(match[v]))
{
match[v] = u;
return 1;
}
}
}
return 0;
}
int main()
{
while(scanf("%d%d", &N, &M) != EOF)
{
for(int i = 1; i <= N; i++)
G[i].clear();
for(int i = 1; i <= M; i++)
{
int a, b;
scanf("%d%d", &a, &b);
G[a].push_back(b);
}
int ans = 0;
memset(match, -1, sizeof(match));
for(int i = 1; i <= N; i++)
{
memset(used, false, sizeof(used));
ans += DFS(i);
}
printf("%d\n", ans);
}
return 0;
}
HK:时间复杂度O(sqrt(N)M)
#include
#include
#include
#include
#define MAXN 5000+10
using namespace std;
int mx[MAXN], my[MAXN];//记录X和Y集元素的匹配点
int dx[MAXN], dy[MAXN];//记录距离标号
bool used[MAXN];//标记是否使用
vector G[MAXN];//存储二分图
int n, m;//点数 边数
int numx, numy;//记录X集元素个数 Y集元素个数
int DFS(int u)//同匈牙利算法 多了层次图的判定
{
for(int i = 0; i < G[u].size(); i++)
{
int v = G[u][i];
if(!used[v] && dy[v] == dx[u] + 1)
{
used[v] = true;
if(my[v] == -1 || DFS(my[v]))
{
my[v] = u;
mx[u] = v;
return 1;
}
}
}
return 0;
}
void HK_match()
{
memset(mx, -1, sizeof(mx));
memset(my, -1, sizeof(my));
int ans = 0;
while(1)
{
bool flag = false;//标记是否找到可增广路径
memset(dx, 0, sizeof(dx));
memset(dy, 0, sizeof(dy));
queue Q;
for(int i = 1; i <= numx; i++)
if(mx[i] == -1) Q.push(i);
while(!Q.empty())//寻找增广路
{
int u = Q.front(); Q.pop();
for(int i = 0; i < G[u].size(); i++)
{
int v = G[u][i];
if(!dy[v])
{
dy[v] = dx[u] + 1;//感觉和 Dinic里面建立层次图很像
if(my[v] == -1)//未匹配 说明找到增广路
flag = true;
else
{
dx[my[v]] = dx[u] + 1;
Q.push(my[v]);
}
}
}
}
if(!flag)//未找到增广路
break;
memset(used, false, sizeof(used));
for(int i = 1; i <= numx; i++)
if(mx[i] == -1)//没有匹配 就开始找匹配
ans += DFS(i);
}
printf("%d\n", ans);//若X、Y集没有详细划分,则numx = numy = n,最后结果除2。
}
int main()
{
while(scanf("%d%d", &n, &m) != EOF)
{
getMap();//建图 用G存储
HK_match();//HK算法
}
return 0;
}
KM 直接上求解最大(小)权匹配:求最小权值匹配,直接将边权取负,最后结果取负就可以了。
时间复杂度O(N*3)
#include
#include
#include
#define MAXN 400
#define INF 100000000//注意INF值要比所有边权值大
using namespace std;
int match[MAXN];//匹配
int lx[MAXN], ly[MAXN];//顶标
int slack[MAXN];//记录T集中节点的松弛量
int Map[MAXN][MAXN];//存储原图S-T节点间的关系
bool visx[MAXN], visy[MAXN];
int nx, ny;//S集中节点数目 T集中节点数目
int DFS(int x)
{
visx[x] = true;
for(int y = 0; y < ny; y++)
{
if(visy[y]) continue;
int t = lx[x] + ly[y] - Map[x][y];//判断边是否在相等子图里面
if(t == 0)//边在相等子图里面
{
visy[y] = true;
if(match[y] == -1 || DFS(match[y]))
{
match[y] = x;
return 1;
}
}
else if(slack[y] > t)// 更新松弛量
slack[y] = t;
}
return 0;
}
int KM()
{
memset(match, -1, sizeof(match));
memset(ly, 0, sizeof(ly));
for(int x = 0; x < nx; x++)//lx[x]初始化为Map[x][y]中最大值
{
lx[x] = -INF;
for(int y = 0; y < ny; y++)
{
if(Map[x][y] > lx[x])
lx[x] = Map[x][y];
}
}
for(int x = 0; x < nx; x++)//匹配
{
for(int i = 0; i < ny; i++)//初始化T集中所有节点的 松弛量
slack[i] = INF;
while(1)
{
memset(visx, false, sizeof(visx));
memset(visy, false, sizeof(visy));
if(DFS(x)) break;//匹配成功
//匹配失败
int d = INF;
//寻找最小的松弛量d
for(int i = 0; i < ny; i++)
{
if(!visy[i] && d > slack[i])
d = slack[i];
}
//修改顶标 为了有更大机会完美匹配
//S集里面所有点 全部减去最小松弛量d
for(int i = 0; i < nx; i++)
{
if(visx[i])
lx[i] -= d;
}
//为了保证S-T的匹配不离开相等子图即对边依旧有lx[x] + ly[y] == Map[x][y]
//T集里面所有点 全部增加最小松弛量d
for(int i = 0; i < ny; i++)
{
if(visy[i]
ly[i] += d;
else//节点的松弛量 减少d
slack[i] -= d;
}
}
}
int res = 0;
for(int i = 0; i < ny; i++)
{
if(match[i] != -1)//有匹配
res += Map[match[i]][i];//累加权值
}
return res;
}
int main()
{
int N;
while(scanf("%d", &N) != EOF)
{
nx = ny = N;
for(int i = 0; i < N; i++)
for(int j = 0; j < N; j++)
scanf("%d", &Map[i][j]);//第i个和第j个匹配的权值
int ans = KM();
printf("%d\n", ans);
}
return 0;
}
求解最大团算法:普通DFS 50个点就要跑8、9s,太慢。
优化DFS算法:
#include
#include
#include
#define MAXN (100+10)
using namespace std;
int N, M;
int Clique[MAXN];//Clique[i]记录(i-N)这些节点可以构成的最大团
int Map[MAXN][MAXN];//存储图
int New[MAXN][MAXN];//每次建立从i-N这些节点 DFS
int used[MAXN], Rec[MAXN];//Rec记录最大团里面的点
int ans;//最大团节点数
int DFS(int T, int cnt)//DFS遍历层次 计数
{
if(T == 0)
{
if(ans < cnt)
{
ans = cnt;
memcpy(Rec, used, sizeof(used));
return 1;
}
return 0;
}
for(int i = 0; i < T; i++)
{
if(T - i + cnt <= ans) return 0;//剪枝一
int u = New[cnt][i];
if(Clique[u] + cnt <= ans) return 0;//剪枝二
int num = 0;
for(int j = i+1; j < T; j++)
if(Map[u][New[cnt][j]])
New[cnt+1][num++] = New[cnt][j];
used[cnt+1] = u;//记录当前节点
if(DFS(num, cnt+1)) return 1;
}
return 0;
}
void MaxClique()
{
memset(Clique, 0, sizeof(Clique));
ans = 0;
for(int i = N; i >= 1; i--)
{
used[1] = i; int Size = 0;
for(int j = i+1; j <= N; j++)//根据后面的节点构建新图
if(Map[i][j])
New[1][Size++] = j;
DFS(Size, 1);
Clique[i] = ans;
}
}
int main()
{
while(scanf("%d%d", &N, &M) != EOF)
{
memset(Map, 0, sizeof(Map));
for(int i = 1; i <= M; i++)
{
int a, b;
scanf("%d%d", &a, &b);
Map[a][b] = Map[b][a] = 1;
}
MaxClique();
printf("%d\n", ans);
for(int i = 1; i <= ans; i++)//输出最大团里面的节点
{
if(i > 1)
printf(" ");
printf("%d", Rec[i]);
}
printf("\n");
}
return 0;
}