二分图匹配
匈牙利算法
- 时间复杂度(O(nm))
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <queue>
using namespace std;
int mp[2100][2100]; // 图的存储矩阵
int n, m;
int ans;
bool vis[2100]; // 当前搜索过程中是否被访问过
int link[2100]; // y集合中的点在x集合中的匹配点 -1表示未匹配
bool find_(int x) {
for (int i=1; i<=n; ++i) {
if (mp[x][i] && !vis[i]) { // 有边相连
vis[i] = 1; // 标记该点
if (link[i] == -1 || find_(link[i])) { //该点未匹配 或者匹配的点能找到增光路
link[i] = x; // 删掉偶数条边 加进奇数条边
return true; // 找到增光路
}
}
}
return false;
}
void match() {
//初始化
ans = 0;
memset(link, -1, sizeof(link));
for (int i=1; i<=n; ++i) {
memset(vis, 0, sizeof(vis)); // 从集合的每个点依次搜索
if (find_(i)) // 如果能搜索到 匹配数加1
ans++;
}
return;
}
int main() {
while(cin >> n >> m) {
memset(mp, 0, sizeof(mp));
for (int i=0; i<m; ++i) {
int x, y;
cin >> x >> y;
mp[x][y] = 1;
mp[y][x] = 1;
}
//判断是不是二分图 过
match();
cout << ans/2 << endl;
}
return 0;
}
HK算法
-
时间复杂度为(O(msqrt n))
-
点的序号要从0开始!
-
需要把nx,ny都赋值为n(点数)
const int MAXN = 1010;
const int MAXM = 1010*1010;
struct Edge {
int v;
int next;
} edge[MAXM];
struct node {
double x, y;
double v;
} a[MAXN], b[MAXN];
int nx, ny;
int cnt;
int t;
int dis;
int first[MAXN];
int xlink[MAXN], ylink[MAXN];
/*xlink[i]表示左集合顶点所匹配的右集合顶点序号,ylink[i]表示右集合i顶点匹配到的左集合顶点序号。*/
int dx[MAXN], dy[MAXN];
/*dx[i]表示左集合i顶点的距离编号,dy[i]表示右集合i顶点的距离编号*/
int vis[MAXN]; //寻找增广路的标记数组
void init() {
cnt = 0;
memset(first, -1, sizeof(first));
memset(xlink, -1, sizeof(xlink));
memset(ylink, -1, sizeof(ylink));
}
void read_graph(int u, int v) {
edge[cnt].v = v;
edge[cnt].next = first[u], first[u] = cnt++;
}
int bfs() {
queue<int> q;
dis = INF;
memset(dx, -1, sizeof(dx));
memset(dy, -1, sizeof(dy));
for(int i = 0; i < nx; i++) {
if(xlink[i] == -1) {
q.push(i);
dx[i] = 0;
}
}
while(!q.empty()) {
int u = q.front();
q.pop();
if(dx[u] > dis) break;
for(int e = first[u]; e != -1; e = edge[e].next) {
int v = edge[e].v;
if(dy[v] == -1) {
dy[v] = dx[u] + 1;
if(ylink[v] == -1) dis = dy[v];
else {
dx[ylink[v]] = dy[v]+1;
q.push(ylink[v]);
}
}
}
}
return dis != INF;
}
int find(int u) {
for(int e = first[u]; e != -1; e = edge[e].next) {
int v = edge[e].v;
if(!vis[v] && dy[v] == dx[u]+1) {
vis[v] = 1;
if(ylink[v] != -1 && dy[v] == dis) continue;
if(ylink[v] == -1 || find(ylink[v])) {
xlink[u] = v, ylink[v] = u;
return 1;
}
}
}
return 0;
}
int MaxMatch() {
int ans = 0;
while(bfs()) {
memset(vis, 0, sizeof(vis));
for(int i = 0; i < nx; i++) if(xlink[i] == -1) {
ans += find(i);
}
}
return ans;
}
调用:
init();
for(int i = 0; i < m; i++) {
if(l[edgee[i][0]] && edgee[i][1] != s && !l[edgee[i][1]]) read_graph(edgee[i][0],edgee[i][1]);
if(l[edgee[i][1]] && edgee[i][0] != s && !l[edgee[i][0]]) read_graph(edgee[i][1],edgee[i][0]);
}
nx = n;
ny = n;
int ans = MaxMatch();
KM算法 BFS优化
最优匹配——定理:设(M)是一个带权完全二分图(G)的一个完备匹配,给每个顶点一个可行顶标(第(i)个(x)顶点的可行顶标用 (x_i)表示,第(j)个y顶点的可行顶标用(y_j)表示),如果对所有的边((i,j)) in (G),都有(x_i+y_j≥w_{i,j})成立((w_{i,j})表示边的权),且对所有的边((i,j)) in (M),都有 成立,则(M)是图(G)的(x_i+y_j≥w_{i,j})一个最优匹配。
-
时间复杂度(O(n^3))
-
最小权值匹配,边权取反,答案取反
-
(x_i+y_j>=w_{i,j}),最小顶标和等价于最大权值匹配
(x_i+y_j<=w_{i,j}),最大顶标和等价于最小权值匹配
typedef long long ll;
const int N = 405;
const ll INF = LONG_LONG_MAX;
struct KM
{
int link_x[N], link_y[N], n, nx, ny;
bool visx[N], visy[N];
int que[N << 1], top, fail, pre[N];
ll mp[N][N], hx[N], hy[N], slk[N];
inline int check(int i)
{
visx[i] =true;
if(link_x[i])
{
que[fail++] = link_x[i];
return visy[link_x[i]] = true;
}
while(i)
{
link_x[i] = pre[i];
swap(i, link_y[pre[i]]);
}
return 0;
}
void bfs(int S)
{
for(int i=1; i<=n; i++)
{
slk[i] = INF;
visx[i] = visy[i] = false;
}
top = 0; fail = 1;
que[0] = S;
visy[S] = true;
while(true)
{
ll d;
while(top < fail)
{
for(int i = 1, j = que[top++]; i <= n; i++)
{
if(!visx[i] && slk[i] >= (d = hx[i] + hy[j] - mp[i][j]))
{
pre[i] = j;
if(d) slk[i] = d;
else if(!check(i)) return;
}
}
}
d = INF;
for(int i=1; i<=n; i++)
{
if(!visx[i] && d > slk[i]) d = slk[i];
}
for(int i=1; i<=n; i++)
{
if(visx[i]) hx[i] += d;
else slk[i] -= d;
if(visy[i]) hy[i] -= d;
}
for(int i=1; i<=n; i++)
{
if(!visx[i] && !slk[i] && !check(i)) return;
}
}
}
void init(int cntx, int cnty)
{
nx = cntx; ny = cnty; n = max(nx, ny);
top = fail = 0;
for (int i = 1; i <= n; i++) {
link_x[i] = link_y[i] = pre[i] = 0;
hx[i] = hy[i] = 0;
for (int j = 1; j <= n; j++) mp[i][j] = 0;
}
}
void solve() {
for(int i=1; i<=n; i++) for(int j=1; j<=n; j++)
if(hx[i] < mp[i][j]) hx[i] = mp[i][j];
ll ans = 0;
for (int i = 1; i <= n; i++) bfs(i);
for (int i = 1; i <= nx; i++) ans += mp[i][link_x[i]];
printf("%lld
", ans); // 输出最大权值
/*
for (int i = 1; i <= nx; i++) if(!mp[i][link_x[i]])
link_x[i] = 0; // 如果不存在该边则置零
for (int i = 1; i <= nx; i++) {
if(i != 1) printf(" ");
printf("%d", link_x[i]); // 输出每一组匹配
}
printf("
");
*/
}
}km;
调用
int n; scanf("%d", &n);
km.init(n, n);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
ll w; scanf("%lld", &w);
km.mp[i][j] = w;
}
}
km.solve();
一般图匹配(带花树)
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=1050;
bool g[maxn][maxn],inque[maxn],inpath[maxn];
bool inhua[maxn];
int st,ed,newbase,ans,n;
int base[maxn],pre[maxn],match[maxn];
int head,tail,que[maxn];
int x[maxn],y[maxn],f[maxn],mp[maxn][maxn],ne,np;
void Push(int u)
{
que[tail]=u;
tail++;
inque[u]=1;
}
int Pop()
{
int res=que[head];
head++;
return res;
}
int lca(int u,int v)//寻找公共花祖先
{
memset(inpath,0,sizeof(inpath));
while(1)
{
u=base[u];
inpath[u]=1;
if(u==st) break;
u=pre[match[u]];
}
while(1)
{
v=base[v];
if(inpath[v]) break;
v=pre[match[v]];
}
return v;
}
void reset(int u)//缩环
{
int v;
while(base[u]!=newbase)
{
v=match[u];
inhua[base[u]]=inhua[base[v]]=1;
u=pre[v];
if(base[u]!=newbase) pre[u]=v;
}
}
void contract(int u,int v)//
{
newbase=lca(u,v);
memset(inhua,0,sizeof(inhua));
reset(u);
reset(v);
if(base[u]!=newbase) pre[u]=v;
if(base[v]!=newbase) pre[v]=u;
for(int i=1;i<=n;i++)
{
if(inhua[base[i]]){
base[i]=newbase;
if(!inque[i])
Push(i);
}
}
}
void findaug()
{
memset(inque,0,sizeof(inque));
memset(pre,0,sizeof(pre));
for(int i=1;i<=n;i++)//并查集
base[i]=i;
head=tail=1;
Push(st);
ed=0;
while(head<tail)
{
int u=Pop();
for(int v=1;v<=n;v++)
{
if(g[u][v]&&(base[u]!=base[v])&&match[u]!=v)
{
if(v==st||(match[v]>0)&&pre[match[v]]>0)//成环
contract(u,v);
else if(pre[v]==0)
{
pre[v]=u;
if(match[v]>0)
Push(match[v]);
else//找到增广路
{
ed=v;
return ;
}
}
}
}
}
}
void aug()
{
int u,v,w;
u=ed;
while(u>0)
{
v=pre[u];
w=match[v];
match[v]=u;
match[u]=v;
u=w;
}
}
void edmonds()//匹配
{
memset(match,0,sizeof(match));
for(int u=1;u<=n;u++)
{
if(match[u]==0)
{
st=u;
findaug();//以st开始寻找增广路
if(ed>0) aug();//找到增广路 重新染色,反向
}
}
}
//以上是带花树求最大匹配算法 不用看
void create()//建图
{
n=0;
memset(g,0,sizeof(g));
for(int i=1;i<=np;i++)
for(int j=1;j<=f[i];j++)
mp[i][j]=++n;//拆点,给每个度的点编号
for(int i=0;i<ne;i++)
{//此时n+1代表x,n+2代表y
for(int j=1;j<=f[x[i]];j++)
g[mp[x[i]][j]][n+1]=g[n+1][mp[x[i]][j]]=1;//每个度的点与对应的x,y相连
for(int j=1;j<=f[y[i]];j++)
g[mp[y[i]][j]][n+2]=g[n+2][mp[y[i]][j]]=1;
g[n+1][n+2]=g[n+2][n+1]=1;//x与y相连
n+=2;
}
}
void print()
{
ans=0;
for(int i=1;i<=n;i++)
if(match[i]!=0)
{
ans++;
// if(match[i]>i)
// cout<<"_____"<<i<<' '<<match[i]<<endl;
}
//cout<<"******"<<ans<<' '<<n<<endl;
if(ans==n) printf("YES
");
else printf("NO
");
}
int main()
{
int t,k=0;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&np,&ne);
for(int i=0;i<ne;i++)
scanf("%d%d",&x[i],&y[i]);
for(int i=1;i<=np;i++)
scanf("%d",&f[i]);
printf("Case %d: ",++k);
create();
edmonds();
print();
}
return 0;
}