二分图的关键点
二分图中的关键点是指,若去掉这个点,使得二分图上的最大匹配变小的方案数。
算法
考虑枚举一个点,将它删去后所产生的影响。
设 m x ( i ) mx(i) mx(i)为 X X X部中的点所匹配到的 Y Y Y部中的点。考虑枚举 i i i,若 m x ( i ) = − 1 mx(i)=-1 mx(i)=−1显然这是个非关键点。
否则我们就沿着 i i i去找一条增广路,若找到了这么一条增广路,就说明这个点是非关键点。
那么剩下的点就是关键点了。
例题:HDU 3517 Adopt or not
题目大意
有 N N N个提案,每种提案有一些人支持,每个人也反对一些提案。若一个人所支持的提案被通过了,且他所反对的提案也被否决了,那么他会开心,否则就会沮丧。求如果让所有人都不沮丧必须通过的提案。
分析
考虑将一个人所支持的提案和他所反对的提案的所有者连边,这样我们就得到了一个二分图。
这样我们就可以将这个问题转成求二分图最大独立集问题,可以用总点数 - 最大匹配数求得。
那么我们需要求的就是最大独立集中的关键点的个数。
简单分析一下就是求二分图中非关键点。于是直接套模板。。。
二分图的关键边
算法
同求解二分图的关键点一样,暴力枚举每条边,然后再跑一遍最大匹配,若答案与原先的最大匹配更小,那么这条边就是关键边,于是记录即可。
例题 HDU 1281 棋盘游戏
题目大意
一个棋盘上只有 K K K个位置能够放车。问:
- 最多能够放上多少辆车使得每行每列仅有一个车;
- 若有些位置上不放车会使得放上车的数量减少,则称这个位置为关键位置,求棋盘上共有多少个这样的关键位置。
分析
根据二分图的一般套路,我们将每行每列建成一个点,对于能放的位置,我们就在对应的行和列的点上面连一条边就是了。
那么我们跑一遍二分图最大匹配就解决了第一个问题。
考虑第二个问题。若删掉一个位置使得最大匹配变小,即删掉该行该列之间所连的边能使得最大匹配变小,这和关键边的定义是相符的。这样一来这道题就变成了求关键边的模板题。
参考代码
HDU 3517
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int Maxn = 200;
bool G[Maxn + 5][Maxn + 5];
int matchx[Maxn + 5], matchy[Maxn + 5];
bool vis[Maxn + 5];
bool DFS(int u, int cnty) {
for(int v = 1; v <= cnty; v++) {
if(vis[v] || !G[u][v]) continue;
vis[v] = true;
if(matchy[v] == -1 || DFS(matchy[v], cnty)) {
matchx[u] = v, matchy[v] = u;
return true;
}
}
return false;
}
int find(int cntx, int cnty) {
memset(matchx, -1, sizeof matchx);
memset(matchy, -1, sizeof matchy);
int ret = 0;
for(int i = 1; i <= cntx; i++) {
memset(vis, false, sizeof vis);
if(DFS(i, cnty)) ret++;
}
return ret;
}
int N, A, B;
int like[Maxn + 5];
vector<int> dislike[Maxn + 5];
int X[Maxn + 5], Y[Maxn + 5];
int delx, dely;
bool JudgeX(int u, int cntx) {
for(int i = 1; i <= cntx; i++) {
if(i == delx || vis[i] || !G[i][u])
continue;
vis[i] = true;
if(matchx[i] == -1 || JudgeX(matchx[i], cntx))
return true;
}
return false;
}
bool JudgeY(int u, int cnty) {
for(int i = 1; i <= cnty; i++) {
if(i == dely || vis[i] || !G[u][i])
continue;
vis[i] = true;
if(matchy[i] == -1 || JudgeY(matchy[i], cnty))
return true;
}
return false;
}
bool remainX[Maxn + 5], remainY[Maxn + 5];
vector<int> Solve(int cntx, int cnty) {
memset(remainX, false, sizeof remainX);
memset(remainY, false, sizeof remainY);
for(int i = 1; i <= cntx; i++) {
if(matchx[i] == -1) {
remainX[i] = true;
continue;
}
dely = matchx[i];
memset(vis, false, sizeof vis);
if(JudgeY(i, cnty)) remainY[dely] = true;
}
for(int i = 1; i <= cnty; i++) {
if(matchy[i] == -1) {
remainY[i] = true;
continue;
}
delx = matchy[i];
memset(vis, false, sizeof vis);
if(JudgeX(i, cntx)) remainX[delx] = true;
}
vector<int> ret;
for(int i = 1; i <= cntx; i++)
if(remainX[i]) ret.push_back(like[X[i]]);
for(int i = 1; i <= cnty; i++)
if(remainY[i]) ret.push_back(like[Y[i]]);
sort(ret.begin(), ret.end());
ret.erase(unique(ret.begin(), ret.end()), ret.end());
return ret;
}
void clear() {
memset(G, false, sizeof G);
memset(dislike, 0, sizeof dislike);
}
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int _;
scanf("%d", &_);
while(_--) {
scanf("%d %d %d", &A, &B, &N);
clear();
int nx = 0, ny = 0;
for(int i = 1; i <= N; i++) {
scanf("%d", &like[i]);
if(like[i] <= A) X[++nx] = i;
else Y[++ny] = i;
int len;
scanf("%d", &len);
while(len--) {
int x;
scanf("%d", &x);
dislike[i].push_back(x);
}
}
for(int i = 1; i <= nx; i++)
for(int j = 1; j <= ny; j++) {
bool flag = false;
for(int k = 0; k < (int)dislike[X[i]].size(); k++)
if(dislike[X[i]][k] == like[Y[j]]) {
flag = true;
break;
}
for(int k = 0; k < (int)dislike[Y[j]].size(); k++)
if(dislike[Y[j]][k] == like[X[i]]) {
flag = true;
break;
}
if(flag) G[i][j] = true;
}
printf("%d\n", N - find(nx, ny));
vector<int> ans = Solve(nx, ny);
printf("%d", ans.size());
for(int i = 0; i < (int)ans.size(); i++)
printf(" %d", ans[i]);
puts("");
}
return 0;
}
HDU 1281
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
const int Maxn = 100;
vector<int> G[Maxn + 5];
bool go[Maxn + 5][Maxn + 5];
void addedge(int u, int v) {
G[u].push_back(v);
}
int N, M, K;
inline void clear() {
memset(go, false, sizeof go);
memset(G, 0, sizeof G);
}
bool vis[Maxn + 5];
int match[Maxn + 5];
bool DFS(int u) {
for(int i = 0; i < (int)G[u].size(); i++) {
int v = G[u][i];
if(vis[v] || !go[u][v])
continue;
vis[v] = true;
if(match[v] == -1 || DFS(match[v])) {
match[v] = u;
return true;
}
}
return false;
}
int find() {
int ret = 0;
for(int i = 1; i <= N; i++) {
memset(vis, false, sizeof vis);
if(DFS(i)) ret++;
}
return ret;
}
int main() {
#ifdef LOACL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
int cnt_case = 1;
while(scanf("%d %d %d", &N, &M, &K) != EOF) {
clear();
for(int i = 1; i <= K; i++) {
int u, v;
scanf("%d %d", &u, &v);
addedge(u, v);
go[u][v] = true;
}
memset(match, -1, sizeof match);
int mx = find(), cnt = 0;
for(int i = 1; i <= N; i++)
for(int j = 1; j <= M; j++) {
if(!go[i][j]) continue;
memset(match, -1, sizeof match);
go[i][j] = false;
if(mx != find()) cnt++;
go[i][j] = true;
}
printf("Board %d have %d important blanks for %d chessmen.\n", cnt_case, cnt, mx);
++cnt_case;
}
return 0;
}