基本二分圖匹配匈牙利算法和一些基本的定理不再闡述
必經\可行點,必經\可行點
必經可行邊在這裏可以看到。
可行點:同可行邊,所有可行邊連接的點,都是可行點,否則不是可行點。
必經點:除暴力枚舉外,我們在跑完網絡流的殘留圖上枚舉所有的未匹配點,進行dfs,記錄所有歷經點,所有沒有被歷經的點,都是必經點。(因爲每個點每條邊最多經歷一邊,所以dfs複雜度是O(n+m) )
二分圖最大邊權匹配
費用流
二分圖多重匹配
最大流
最大積匹配
我們想找到最大權匹配,而這個最大最大權是指最大的邊權之積。我們可以取自然對數,然後跑最大權匹配。但是注意精度問題。
一般圖最大匹配(帶花樹)
用於一般圖的最大匹配。
【經典模型】:棋盤博弈,兩個人互相從棋盤取棋子,每個人取得棋子只能是上一個人取棋子的曼哈頓距離小於L的棋子。誰沒法取了就輸了。第一個棋子隨便取。我們將所有距離小於L的棋子連邊建圖。最後如果有一個聯通塊不是完備匹配,先手就贏了。先手可以取沒法必備的那個點,然後每次取先手的匹配點即可。
/*
帶花樹, 求一般圖的最大匹配:
我們思考求二分圖的匈牙利算法爲啥不適合一般圖:一般圖有
奇環, 在匈牙利算法尋找交錯路增廣的時候,所有尋找到未匹
配的終點時, 都可以變換交錯路的邊,實現增廣但是, 在一般
圖中, 我們尋找到一個終點,因爲交錯路中存在的奇環, 我們
不能保證這次增廣的正確性,(在紙上畫畫就可知)所以我們處
理奇環情況:
在一個大小爲(2k+1)的奇環中, 最多匹配k對頂點, 另一個必
須連接外面, 所以我們考慮將奇環所成一個點, 具體做法就
是利用並查集的合併.
注意事項: 複雜度O(n^3) 點的編號必須從一開始, 否則初始
化pre[] = -1.
*/
//洛谷 P6113
const int N = 1e3+5;
const int M = 1e5+5;
int n, m, pre[N], tim = 0;
queue<int> q;
int he[N], ne[M], ver[M], tot;
int match[N], fa[N], tp[N], tic[N];
int fi(int x)
{ // 並查集尋根
if (fa[x] == x) return x;
return fa[x] = fi(fa[x]);
}
inline void add(int x, int y)
{
ver[++tot] = y;
ne[tot] = he[x];
he[x] = tot;
}
int lca(int x, int y)
{ // 找lca
tim++;
for (; ; swap(x, y)) if (x) // 點的編號要從1開始
{ // 暴力跳祖先, x, y依次跳躍以減小跳躍次數
x = fi(x);
if (tic[x] == tim) return x;
tic[x] = tim, x = pre[match[x]];
}
}
void mer (int x, int y, int _lca)
{
while(fi(x) != _lca)
{ // 合併 ( 縮點 / [ 開 花 ] )
pre[x] = y; y = match[x];
if (tp[y] == 2) tp[y] = 1; q.push(y);
if (fi(x) == x) fa[x] = _lca;
if (fi(y) == y) fa[y] = _lca;
x = pre[y];
}
}
int flowerdance (int s)
{
for (int i = 1; i <= n; i++) fa[i] = i;
memset(tp, 0, sizeof(tp)); memset(pre, 0, sizeof(pre));
while(q.size()) q.pop();
tp[s] = 1; q.push(s);
while(q.size())
{ // 從s點開始bfs, 一次尋找增廣路的操作.
int x = q.front(); q.pop();
for (int i = he[x]; i; i = ne[i])
{
int y = ver[i];
if (fi(y) == fi(x) || tp[y] == 2) continue;
// 如果y x已經在一朵花裏, 或者y已經被搜過, 且形成的是偶環, 直接無視
if (!tp[y])
{ // y沒有被搜過.
tp[y] = 2; pre[y] = x;
// y染色爲2, y入點爲x
if (!match[y])
{ // 如果y沒有被匹配過, 增廣路成功, 回溯記錄每一對match
int la, te;
for (int now = y; now; now = la)
{
la = match[te = pre[now]];
match[now] = te; match[te] = now;
}
return 1;
}
tp[match[y]] = 1; q.push(match[y]);
// 如果y被匹配過, 把y染色1, 並丟到隊列, 繼續搜索.
}
else if (tp[y] == 1)
{ // y的顏色爲1, 出現奇環
int l = lca(x, y); // 找lca
mer(x, y, l);
mer(y, x, l); // 開花
}
}
}
return 0;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; i++)
{
int x, y; scanf("%d%d", &x, &y);
add(x, y); add(y, x);
}
int ans = 0;
for (int i = 1; i <= n; i++) ans += (!match[i] && flowerdance(i));
printf("%d\n", ans);
for (int i = 1; i <= n; i++) printf("%d ", match[i]);
return 0;
}
一般圖的最大、最小權完全匹配
注意這裏是完全匹配
/*
一般圖最大\最小權, 完全匹配
節點編號必須從0開始.
如果要求最大匹配, 邊權爲正. 否則爲負.
*/
// poj 2404
const int N = 128;
const int inf = 0x3f3f3f3f;
int n, m;
int deg[N], e[N][N];
int cnt; int g[N][N];
int dis[N]; bool vis[N];
int rec[N], tot, M[N];
bool Dij(int x)
{
rec[tot++] = x;
if (vis[x]) return 1;
vis[x] = 1;
for (int y = 0; y < cnt; y++)
{
if (y != x && M[x] != y && !vis[y])
{
int w = M[y];
if (dis[w] < dis[x] + g[x][y] - g[y][w])
{
dis[w] = dis[x] + g[x][y] - g[y][w];
if (Dij(w)) return 1;
}
}
}
tot--;
vis[x] = 0;
return 0;
}
int match()
{
for (int i = 0; i < cnt; i+=2) M[i] = i+1, M[i+1] = i;
int _cnt = 0;
while(1)
{
tot = 0;
bool flag = 0;
memset(dis, 0, sizeof(dis));
memset(vis, 0, sizeof(vis));
for (int i = 0; i < cnt; i++)
{
if (Dij(i))
{
flag = 1;
int nx = M[rec[tot-1]];
int j;
for (j = tot - 2; rec[j] != rec[tot-1]; j--)
{
M[nx] = rec[j];
swap(nx, M[rec[j]]);
}
M[nx] = rec[j];
M[rec[j]] = nx;
break;
}
}
if (!flag)
{
_cnt++;
if (_cnt >= 3) break;
}
}
int sum = 0;
for (int i = 0; i < cnt; i++)
{
int y = M[i];
if (i < y) sum += g[i][y];
}
return sum;
}
void init()
{
memset(deg, 0, sizeof(deg));
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) e[i][j] = inf;
}
void floyd ()
{
for (int k = 0; k < n; k++)
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
e[i][j] = min(e[i][j], e[i][k] + e[k][j]);
}
int main()
{
while(scanf("%d", &n), n)
{
init();
scanf("%d", &m);
int sum = 0;
while(m--)
{
int x, y, w; scanf("%d%d%d", &x, &y, &w);
x--; y--;
e[x][y] = e[y][x] = min(e[x][y], w);
sum += w;
deg[x]++; deg[y]++;
}
int V[N], ct = 0;
for (int i = 0; i < n; i++) if (deg[i] & 1) V[ct++] = i;
floyd();
cnt = ct;
for (int i = 0; i < ct; i++)
for (int j = 0; j < ct; j++)
g[i][j] = -e[V[i]][V[j]]; //求最小權就壓負得, 求最大權就壓正的
printf("%d\n", sum - match());
}
return 0;
}