mirror of
https://github.com/Kiritow/OJ-Problems-Source.git
synced 2024-03-22 13:11:29 +08:00
358 lines
12 KiB
C++
358 lines
12 KiB
C++
|
//图论点、边集和二分图的相关概念和性质
|
|||
|
//点覆盖: 点覆盖集即一个点集, 使得所有边至少有一个端点在集合里
|
|||
|
//边覆盖: 边覆盖集即一个边集, 使得所有点都与集合里的边邻接
|
|||
|
//独立集: 独立集即一个点集, 集合中任两个结点不相邻, 则称V为独立集
|
|||
|
//团: 团即一个点集, 集合中任两个结点相邻
|
|||
|
//边独立集: 边独立集即一个边集, 满足边集中的任两边不邻接
|
|||
|
//支配集: 支配集即一个点集, 使得所有其他点至少有一个相邻点在集合里
|
|||
|
//边支配集: 边支配集即一个边集, 使得所有边至少有一条邻接边在集合里
|
|||
|
//最小路径覆盖: 用尽量少的不相交简单路径覆盖有向无环图G的所有顶点, 即每个顶点严格属于一条路径, 路径的长度可能为0(单个点)
|
|||
|
//匹配: 匹配是一个边集, 满足边集中的边两两不邻接
|
|||
|
//在匹配中的点称为匹配点或饱和点;反之, 称为未匹配点或未饱和点
|
|||
|
//交错轨(alternating path): 图的一条简单路径, 满足任意相邻的两条边, 一条在匹配内, 一条不在匹配内
|
|||
|
//增广轨(augmenting path): 是一个始点与终点都为未匹配点的交错轨
|
|||
|
//最大匹配(maximum matching): 具有最多边的匹配
|
|||
|
//匹配数(matching number): 最大匹配的大小
|
|||
|
//完美匹配(perfect matching): 匹配了所有点的匹配
|
|||
|
//完备匹配(complete matching): 匹配了二分图较小集合(二分图X, Y中小的那个)的所有点的匹配
|
|||
|
//增广轨定理: 一个匹配是最大匹配当且仅当没有增广轨
|
|||
|
//所有匹配算法都是基于增广轨定理: 一个匹配是最大匹配当且仅当没有增广轨
|
|||
|
//最大匹配数 = 最小覆盖数 = 左边匹配点 + 右边未匹配点
|
|||
|
//最小边覆盖 = 图中点的个数 - 最大匹配数 = 独立数
|
|||
|
|
|||
|
//二分图最大匹配
|
|||
|
//Hungary + dfs
|
|||
|
//邻接矩阵 O(V^2)
|
|||
|
int uN, vN, match[N]; //左右点的数目
|
|||
|
bool g[N][N], check[N];
|
|||
|
bool dfs(int u) {
|
|||
|
for (int v = 0; v < vN; v++) {
|
|||
|
if (g[u][v] && !check[v]) {
|
|||
|
check[v] = true;
|
|||
|
if (match[v] == -1 || dfs(match[v])) {
|
|||
|
match[v] = u; match[u] = v; return true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
//邻接表 O(V*E)
|
|||
|
int head[N], to[M], nxt[M], tot, uN, match[N];
|
|||
|
bool check[N];
|
|||
|
void init() { tot = 0; memset(head, -1, sizeof(head)); }
|
|||
|
void addedge(int x, int y) { to[tot] = y; nxt[tot] = head[x]; head[x] = tot++; }
|
|||
|
bool dfs(int u) {
|
|||
|
for (int i = head[u]; ~i; i = nxt[i]) {
|
|||
|
int v = to[i];
|
|||
|
if (!check[v]) {
|
|||
|
check[v] = true;
|
|||
|
if (match[v] == -1 || dfs(match[v])) {
|
|||
|
match[v] = u; match[u] = v; return true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
int Hungary() {
|
|||
|
memset(match, -1, sizeof(match));
|
|||
|
int res = 0;
|
|||
|
for (int u = 0; u < uN; u++) {
|
|||
|
if (match[u] == -1) {
|
|||
|
memset(check, 0, sizeof(check));
|
|||
|
if (dfs(u)) { res++; }
|
|||
|
}
|
|||
|
}
|
|||
|
return res;
|
|||
|
}
|
|||
|
//Hungary + bfs + 邻接矩阵 O(V*E)
|
|||
|
bool g[N][N];
|
|||
|
int uN, vN, match[N], check[N], pre[N];
|
|||
|
int Hungary() {
|
|||
|
memset(match, -1, sizeof(match));
|
|||
|
memset(check, -1, sizeof(check));
|
|||
|
queue<int> que;
|
|||
|
int res = 0;
|
|||
|
for (int i = 0; i < uN; i++) {
|
|||
|
if (match[i] == -1) {
|
|||
|
while (!que.empty()) { que.pop(); }
|
|||
|
que.push(i); pre[i] = -1;
|
|||
|
bool flag = false;
|
|||
|
while (!que.empty() && !flag) {
|
|||
|
int u = que.front(); que.pop();
|
|||
|
for (int v = 0; v < vN && !flag; v++) {
|
|||
|
if (g[u][v] && check[v] != i) {
|
|||
|
check[v] = i; que.push(match[v]);
|
|||
|
if (~match[v]) { pre[match[v]] = u; }
|
|||
|
else {
|
|||
|
flag = true;
|
|||
|
for (int a = u, b = v; ~a;) {
|
|||
|
int t = match[a]; match[a] = b; match[b] = a; a = pre[a]; b = t;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if (~match[i]) { res++; }
|
|||
|
}
|
|||
|
}
|
|||
|
return res;
|
|||
|
}
|
|||
|
//Hungary + bfs + 邻接表 O(V*E)
|
|||
|
int head[N], to[M], nxt[M], tot, uN, match[N], check[N], pre[N];
|
|||
|
void init() { tot = 0; memset(head, -1, sizeof(head)); }
|
|||
|
void addedge(int x, int y) { to[tot] = y; nxt[tot] = head[x]; head[x] = tot++; }
|
|||
|
int Hungary() {
|
|||
|
memset(match, -1, sizeof(match));
|
|||
|
memset(check, -1, sizeof(check));
|
|||
|
queue<int> que;
|
|||
|
int res = 0;
|
|||
|
for (int i = 0; i < uN; i++) {
|
|||
|
if (match[i] == -1) {
|
|||
|
while (!que.empty()) { que.pop(); }
|
|||
|
que.push(i); pre[i] = -1;
|
|||
|
bool flag = false;
|
|||
|
while (!que.empty() && !flag) {
|
|||
|
int u = que.front(); que.pop();
|
|||
|
for (int i = head[u]; ~i && !flag; i = nxt[i]) {
|
|||
|
int v = to[i];
|
|||
|
if (check[v] != i) {
|
|||
|
check[v] = i; que.push(match[v]);
|
|||
|
if (~match[v]) { pre[match[v]] = u; }
|
|||
|
else {
|
|||
|
flag = true;
|
|||
|
for (int a = u, b = v; ~a;) {
|
|||
|
int t = match[a]; match[a] = b; match[b] = a; a = pre[a]; b = t;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if (~match[i]) { res++; }
|
|||
|
}
|
|||
|
}
|
|||
|
return res;
|
|||
|
}
|
|||
|
//Hopcroft-Karp + vector + O(sqrt(V)*E)
|
|||
|
const int INF = 0x3f3f3f3f;
|
|||
|
vector<int> g[N];
|
|||
|
int uN, matchx[N], matchy[N], dx[N], dy[N], dis;
|
|||
|
bool check[N];
|
|||
|
bool SearchP() {
|
|||
|
memset(dx, -1, sizeof(dx));
|
|||
|
memset(dy, -1, sizeof(dy));
|
|||
|
queue<int> que; dis = INF;
|
|||
|
for (int i = 0; i < uN; i++) { if (matchx[i] == -1) { dx[i] = 0; que.push(i); } }
|
|||
|
while (!que.empty()) {
|
|||
|
int u = que.front(); que.pop();
|
|||
|
if (dx[u] > dis) { break; }
|
|||
|
for (size_t i = 0; i < g[u].size(); i++) {
|
|||
|
int v = g[u][i];
|
|||
|
if (dy[v] == -1) {
|
|||
|
dy[v] = dx[u] + 1;
|
|||
|
if (matchy[v] == -1) { dis = dy[v]; }
|
|||
|
else { dx[matchy[v]] = dy[v] + 1; que.push(matchy[v]); }
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return dis != INF;
|
|||
|
}
|
|||
|
bool dfs(int u) {
|
|||
|
for (size_t i = 0; i < g[u].size(); i++) {
|
|||
|
int v = g[u][i];
|
|||
|
if (!check[v] && dy[v] == dx[u] + 1) {
|
|||
|
check[v] = true;
|
|||
|
if (~matchy[v] && dy[v] == dis) { continue; }
|
|||
|
if (matchy[v] == -1 || dfs(matchy[v])) {
|
|||
|
matchy[v] = u; matchx[u] = v; return true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
int HK() {
|
|||
|
memset(matchx, -1, sizeof(matchx));
|
|||
|
memset(matchy, -1, sizeof(matchy));
|
|||
|
int res = 0;
|
|||
|
while (SearchP()) {
|
|||
|
memset(check, 0, sizeof(check));
|
|||
|
for (int i = 0; i < uN; i++) {
|
|||
|
if (matchx[i] == -1 && dfs(i)) { res++; }
|
|||
|
}
|
|||
|
}
|
|||
|
return res;
|
|||
|
}
|
|||
|
//二分图最佳匹配
|
|||
|
//Kuhn-Munkers + 邻接矩阵 O(uN^2*vN)
|
|||
|
//若求最小权匹配,可将权值取相反数,结果取相反数 点的编号从0开始
|
|||
|
const int INF = 0x3f3f3f3f;
|
|||
|
int uN, vN, g[N][N];
|
|||
|
int matchy[N], lx[N], ly[N]; //y中各点匹配状态, 可行顶标
|
|||
|
int slack[N]; //松弛数组
|
|||
|
bool visx[N], visy[N];
|
|||
|
bool dfs(int u) {
|
|||
|
visx[u] = true;
|
|||
|
for (int v = 0; v < vN; v++) {
|
|||
|
if (visy[v]) { continue; }
|
|||
|
int tmp = lx[u] + ly[v] - g[u][v];
|
|||
|
if (tmp == 0) {
|
|||
|
visy[v] = true;
|
|||
|
if (matchy[v] == -1 || dfs(matchy[v])) { matchy[v] = u; return true; }
|
|||
|
} else if (slack[v] > tmp) { slack[v] = tmp; }
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
int KM() {
|
|||
|
memset(matchy, -1, sizeof(matchy));
|
|||
|
memset(ly, 0, sizeof(ly));
|
|||
|
for (int i = 0; i < uN; i++) { lx[i] = *max_element(g[i], g[i] + vN); }
|
|||
|
for (int u = 0; u < uN; u++) {
|
|||
|
memset(slack, 0x3f, sizeof(slack));
|
|||
|
for (;;) {
|
|||
|
memset(visx, 0, sizeof(visx));
|
|||
|
memset(visy, 0, sizeof(visy));
|
|||
|
if (dfs(u)) { break; }
|
|||
|
int d = INF;
|
|||
|
for (int i = 0; i < vN; i++) { if (!visy[i] && d > slack[i]) { d = slack[i]; } }
|
|||
|
for (int i = 0; i < uN; i++) { if (visx[i]) { lx[i] -= d; } }
|
|||
|
for (int i = 0; i < vN; i++) { if (visy[i]) { ly[i] += d; } else { slack[i] -= d; } }
|
|||
|
}
|
|||
|
}
|
|||
|
int res = 0;
|
|||
|
for (int i = 0; i < vN; i++) { if (matchy[i] != -1) { res += g[matchy[i]][i]; } }
|
|||
|
return res;
|
|||
|
}
|
|||
|
//二分图多重匹配
|
|||
|
int uN, vN, g[N][N], match[N][N]; //match[i][0]为个数
|
|||
|
int num[N]; //右边最大的匹配数
|
|||
|
bool check[N];
|
|||
|
bool dfs(int u) {
|
|||
|
for (int v = 0; v < vN; v++) {
|
|||
|
if (g[u][v] && !check[v]) {
|
|||
|
check[v] = true;
|
|||
|
if (match[v][0] < num[v]) { match[v][++match[v][0]] = u; return true; }
|
|||
|
for (int i = 1; i <= num[0]; i++) {
|
|||
|
if (dfs(match[v][i])) { match[v][i] = u; return true; }
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
int Hungary() {
|
|||
|
int res = 0;
|
|||
|
for (int i = 0; i < vN; i++) { match[i][0] = 0; }
|
|||
|
for (int u = 0; u < uN; u++) {
|
|||
|
memset(check, false, sizeof(check));
|
|||
|
res += dfs(u);
|
|||
|
}
|
|||
|
return res;
|
|||
|
}
|
|||
|
//一般图最大匹配
|
|||
|
//邻接矩阵 O(V^3)
|
|||
|
int n, match[N];
|
|||
|
bool g[N][N], check[N];
|
|||
|
bool aug(int now) {
|
|||
|
bool ret = false; check[now] = true;
|
|||
|
for (int i = 0; i < n; i++) {
|
|||
|
if (!check[i] && g[now][i]) {
|
|||
|
if (match[i] == -1) { match[now] = i; match[i] = now; ret = true; }
|
|||
|
else {
|
|||
|
check[i] = true;
|
|||
|
if (aug(match[i])) { match[now] = i, match[i] = now, ret = true; }
|
|||
|
check[i] = false;
|
|||
|
}
|
|||
|
if (ret) { break; }
|
|||
|
}
|
|||
|
}
|
|||
|
check[now] = false; return ret;
|
|||
|
}
|
|||
|
//邻接表 O(V*E)
|
|||
|
int head[N], to[M], nxt[M], tot, match[N];
|
|||
|
bool check[N];
|
|||
|
void init() { tot = 0; memset(head, -1, sizeof(head)); }
|
|||
|
void addedge(int x, int y) { to[tot] = y; nxt[tot] = head[x]; head[x] = tot++; }
|
|||
|
bool aug(int now) {
|
|||
|
bool ret = false; check[now] = true;
|
|||
|
for (int i = head[now]; ~i; i = nxt[i]) {
|
|||
|
int v = to[i];
|
|||
|
if (!check[v]) {
|
|||
|
if (match[v] == -1) { match[now] = v; match[v] = now; ret = true; }
|
|||
|
else {
|
|||
|
check[v] = true;
|
|||
|
if (aug(match[v])) { match[now] = v; match[v] = now; ret = true; }
|
|||
|
check[v] = false;
|
|||
|
}
|
|||
|
if (ret) { break; }
|
|||
|
}
|
|||
|
}
|
|||
|
check[now] = false; return ret;
|
|||
|
}
|
|||
|
int graphMatch() {
|
|||
|
memset(match, -1, sizeof(match));
|
|||
|
memset(check, 0, sizeof(check));
|
|||
|
for (int i = 0, j = n; i < n && j >= 2; i++) {
|
|||
|
if (match[i] == -1 && aug(i)) { i = -1, j -= 2; }
|
|||
|
}
|
|||
|
int ret = 0;
|
|||
|
for (int i = 0; i < n; i++) { ret += (match[i] != -1); }
|
|||
|
return ret >> 1;
|
|||
|
}
|
|||
|
//带花树 + vector O(V^3) 点的编号从0开始
|
|||
|
int n, match[N], fa[N], nxt[N], mark[N], vis[N], t;
|
|||
|
vector<int> e[N]; queue<int> que;
|
|||
|
int findfa(int n) { return n == fa[n] ? n : fa[n] = findfa(fa[n]); }
|
|||
|
void unite(int x, int y) { fa[findfa(x)] = findfa(y); }
|
|||
|
int lca(int x, int y) {
|
|||
|
for (t++;; swap(x, y)) {
|
|||
|
if (x == -1) { continue; }
|
|||
|
if (vis[x = findfa(x)] == t) { return x; }
|
|||
|
vis[x] = t; x = match[x] == -1 ? -1 : nxt[match[x]];
|
|||
|
}
|
|||
|
}
|
|||
|
void group(int a, int p) {
|
|||
|
for (int b, c; a != p; unite(a, b), unite(b, c), a = c) {
|
|||
|
b = match[a]; c = nxt[b];
|
|||
|
if (findfa(c) != p) { nxt[c] = b; }
|
|||
|
if (mark[b] == 2) { mark[b] = 1; que.push(b); }
|
|||
|
if (mark[c] == 2) { mark[c] = 1; que.push(c); }
|
|||
|
}
|
|||
|
}
|
|||
|
void aug(int st) {
|
|||
|
for (int i = 0; i < n; i++) { fa[i] = i; }
|
|||
|
memset(nxt, -1, sizeof(nxt)); memset(vis, -1, sizeof(vis)); memset(mark, 0, sizeof(mark));
|
|||
|
while (!que.empty()) { que.pop(); } que.push(st); mark[st] = 1;
|
|||
|
while (match[st] == -1 && !que.empty()) {
|
|||
|
int u = que.front(); que.pop();
|
|||
|
for (int i = 0; i < (int)e[u].size(); i++) {
|
|||
|
int v = e[u][i];
|
|||
|
if (v == match[u] || findfa(u) == findfa(v) || mark[v] == 2) { continue; }
|
|||
|
if (mark[v] == 1) {
|
|||
|
int p = lca(u, v);
|
|||
|
if (findfa(u) != p) { nxt[u] = v; }
|
|||
|
if (findfa(v) != p) { nxt[v] = u; }
|
|||
|
group(u, p); group(v, p);
|
|||
|
} else if (match[v] == -1) {
|
|||
|
nxt[v] = u;
|
|||
|
for (int j = v, k, l; ~j; j = l) { k = nxt[j]; l = match[k]; match[j] = k; match[k] = j; }
|
|||
|
break;
|
|||
|
} else { nxt[v] = u; que.push(match[v]); mark[match[v]] = 1; mark[v] = 2; }
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
int solve(int n) {
|
|||
|
memset(match, -1, sizeof(match)); t = 0; int ret = 0;
|
|||
|
for (int i = 0; i < n; i++) { if (match[i] == -1) { aug(i); } }
|
|||
|
for (int i = 0; i < n; i++) { ret += (match[i] > i); }
|
|||
|
return ret;
|
|||
|
}
|
|||
|
//ural 1099
|
|||
|
int main() {
|
|||
|
int u, v;
|
|||
|
scanf("%d", &n);
|
|||
|
while (~scanf("%d%d", &u, &v)) { e[--u].push_back(--v); e[v].push_back(u); }
|
|||
|
int ans = solve(n);
|
|||
|
printf("%d\n", ans * 2);
|
|||
|
for (int u = 0; u < n; u++) {
|
|||
|
if (u < match[u]) { printf("%d %d\n", u + 1, match[u] + 1); }
|
|||
|
}
|
|||
|
}
|