0x69 圖論-二分圖的覆蓋與獨立集
A:Machine Schedule
輸入
5 5 10
0 1 1
1 1 2
2 1 3
3 1 4
4 2 1
5 2 2
6 2 3
7 2 4
8 3 3
9 4 3
0
輸出
3
在二分圖中我們經常要找題目中的 “0要素” 和 “1要素” ,作為解答的突破口。
二分圖最小覆蓋模型的特點則是:每條邊有2個端點,二者至少選擇一個。我們不妨稱之為 “2元素”。
如果題目具有 “2元素” 的特點,那麼可以嘗試抽象成二分圖的最小覆蓋模型求解。
AC程式碼
#include<bits/stdc++.h> using namespace std; const int N = 110; int n, m, k, f[N], ans; bool v[N]; vector<int>e[N]; bool dfs(int x) { for (int i = 0; i < e[x].size(); ++i) { int y = e[x][i]; if (v[y])continue; v[y] = true; if (!f[y] || dfs(f[y])) { f[y] = x; return 1; } } return 0; } int main() { //freopen("in.txt", "r", stdin); while (cin >> n && n) { cin >> m >> k; for (int i = 0; i < n; ++i)e[i].clear(); for (int i = 0; i < k; ++i) { int x, y; cin >> i >> x >> y; if (x && y)e[x].push_back(y); } memset(f, 0, sizeof f); ans = 0; for (int i = 1; i < n; ++i) { memset(v, 0, sizeof v); ans += dfs(i); } cout << ans << endl; } }
B. Muddy Fields
題目描述
輸入
4 4
*.*.
.***
***.
..*.
輸出
4
備註:
OUTPUT DETAILS:
Boards 1, 2, 3 and 4 are placed as follows:
1.2.
.333
444.
..2.
Board 2 overlaps boards 3 and 4.
題目大意:用木板將'*'覆蓋,同一行或同一列的'*'可以用一塊木板覆蓋,'.'不能被覆蓋。問最少用多少塊木板可以把全部的'*'覆蓋?
木板只能夠覆蓋連續的橫著的泥巴和豎著的泥巴,中間有草地就要隔開
解題思路:二分匹配的經典構圖題目
構圖思路:
將橫著的木板和看成一邊的點的集合,將豎著的木板看成另外一邊的點的集合,如果他們相交於一點就連邊
如果要把所有的泥巴覆蓋,又要所需要的木板最少,那麼就是求最小點覆蓋
所以用匈牙利求最大匹配數即可
構圖的程式碼要好好再看看!
#include<bits/stdc++.h> using namespace std; const int N = 56; int n, m, tot = 1, a[N][N][2], f[N * N], ans; char s[N][N]; bool v[N * N]; vector<int> e[N * N]; bool dfs(int x) { for (unsigned int i = 0; i < e[x].size(); i++) { int y = e[x][i]; if (v[y]) continue; v[y] = 1; if (!f[y] || dfs(f[y])) { f[y] = x; return 1; } } return 0; } int main() { //freopen("in.txt", "r", stdin); cin >> n >> m; for (int i = 1; i <= n; i++) scanf("%s", s[i] + 1); for (int i = 1; i <= n; i++) for (int j = 1; j <= m + 1; j++)//m + 1 if (s[i][j] == '*') a[i][j][0] = tot; else ++tot; int t = tot; for (int j = 1; j <= m; j++) for (int i = 1; i <= n + 1; i++) if (s[i][j] == '*') a[i][j][1] = tot; else ++tot; for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) if (s[i][j] == '*') { e[a[i][j][0]].push_back(a[i][j][1]); e[a[i][j][1]].push_back(a[i][j][0]); } for (int i = 1; i < t; i++) { memset(v, 0, sizeof(v)); ans += dfs(i); } cout << ans << endl; }
C. 騎士放置 (二分圖的最大獨立集)
題目描述
輸入
2 3 0
輸出
4
若兩個格子是“日”字的對角(能相互攻擊),則在它們對應的節點之間連邊。
#include<bits/stdc++.h>
using namespace std;
const int N = 105;
int n, m, t, ans, fx[N][N], fy[N][N];
bool a[N][N], v[N][N];
const int dx[8] = { -2, -2, -1, -1, 1, 1, 2, 2 };
const int dy[8] = { -1, 1, -2, 2, -2, 2, -1, 1 };
bool dfs(int x, int y) {
for (int i = 0; i < 8; i++) {
int nx = x + dx[i], ny = y + dy[i];
if (nx < 1 || ny < 1 || nx > n || ny > m || a[nx][ny]) continue;
if (v[nx][ny]) continue;
v[nx][ny] = 1;
if (fx[nx][ny] == 0 || dfs(fx[nx][ny], fy[nx][ny])) {
fx[nx][ny] = x, fy[nx][ny] = y;
return true;
}
}
return false;
}
int main() {
//freopen("in.txt", "r", stdin);
cin >> n >> m >> t;
for (int i = 0; i < t; ++i) {
int x, y; cin >> x >> y;
a[x][y] = 1;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (i + j & 1) continue;
if (a[i][j]) continue;
memset(v, 0, sizeof(v));
if (dfs(i, j)) ans++;
}
}
cout << n * m - t - ans << endl;
}
再貼一個網路流的寫法 7ms,不得不說匈牙利演算法在解決這類問題稍微時間複雜度大了點
#include<bits/stdc++.h>
using namespace std;
#define MAXN 60555
#define MAXM 520555
int N, M, TT;
int hd[MAXN], val[MAXM], nxt[MAXM], to[MAXM], tot(1);
int cur[MAXN];
int S, T, x, y;
bool mp[205][205];
void Add( int x, int y, int z ){
nxt[++tot] = hd[x]; hd[x] = tot; to[tot] = y; val[tot] = z;
nxt[++tot] = hd[y]; hd[y] = tot; to[tot] = x; val[tot] = 0;
}
int d[MAXN]; queue<int> Q;
bool BFS(){
while( !Q.empty() ) Q.pop();
memset( d, 0, sizeof d ); d[S] = 1; Q.push(S);
while( !Q.empty() ){
int t(Q.front()); Q.pop();
for ( int i = hd[t]; i; i = nxt[i] ){
if ( val[i] && !d[to[i]] ){
d[to[i]] = d[t] + 1; Q.push(to[i]);
if ( to[i] == T ) return 1;
}
}
}
return 0;
}
int DFS( int x, int fl ){
if ( x == T ) return fl;
int res(fl), k;
for ( int &i = cur[x]; i && res; i = nxt[i] ){
if ( val[i] && d[to[i]] == d[x] + 1 ){
k = DFS( to[i], min( res, val[i] ) );
if ( !k ) d[to[i]] = 0;
res -= k; val[i] -= k; val[i ^ 1] += k;
}
}
return fl - res;
}
int GetID( int x, int y ){ return ( x - 1 ) * M + y; }
int dir[][2] = { 1, 2, 2, 1, -1, -2, -2, -1, -1, 2, 2, -1, 1, -2, -2, 1 };
int main(){
scanf( "%d%d%d", &N, &M, &TT );
for ( int i = 1; i <= TT; ++i ) scanf( "%d%d", &x, &y ), mp[x][y] = 1;
S = 0; T = N * M + 1;
for ( int i = 1; i <= N; ++i )
for ( int j = 1; j <= M; ++j ){
if ( mp[i][j] ) continue;
if ( ( i ^ j ) & 1 ) Add( S, GetID( i, j ), 1 );
else{
Add( GetID( i, j ), T, 1 );
for ( int k = 0; k < 8; ++k ){
int tx(i + dir[k][0]), ty(j + dir[k][1]);
if ( tx > 0 && ty > 0 && tx <= N && ty <= M && !mp[tx][ty] ) Add( GetID( tx, ty ), GetID( i, j ), INT_MAX );
}
}
}
int ans( N * M - TT ), k;
while( BFS() ){
memcpy( cur, hd, sizeof hd );
while( ( k = DFS( S, INT_MAX ) ) ) ans -= k;
}
printf( "%d\n", ans );
return 0;
}
D. Vani和cl2捉迷藏 有向無環圖的最小路徑點覆蓋
題目描述
輸入
7 5
1 2
3 2
2 4
4 5
4 6
輸出
3
首先明確,求解的是一個最大的點集,滿足集合中的點中任意兩個點之間沒有通路。ohhhh???這不是最大獨立集嗎???可惜這是個有向圖,最大獨立集也是針對無向圖來說的,如果你去找二分圖的定義,會發現,前面都有一個前提,一個無向圖怎樣怎樣,也就是說二分圖是對無向圖而言的。那這個有向圖就不能當作最大獨立集考慮了。
但是我們知道有向圖有最小路徑點覆蓋,定義如下:選取最少的不相交的邊覆蓋全部頂點。最小路徑可重複覆蓋:選取最少可相交的邊覆蓋全部頂點。對於這個路徑的集合,每次從裡面最多挑出來一個點,如果挑出來多餘一個點,以兩個為例,那麼這兩個點之間肯定有一條簡單路徑可以連線,也就是說二者肯定有一方可以到達另一方。那麼我們從所有的路徑中,每個路徑挑一個點,最後就組成了這個最大的點集合。
還有一點需要說明,為什麼這個問題是一個最小路徑可重複覆蓋,因為題目中有說,如果一個點沿著某一路徑走下去可以到達另一個點,則這兩個點也是互相可以望見的,也就是說對於邊a->b,b->c,間接的a->c也算。所以就先floyd求一下傳遞閉包,建立拆點二分圖,然後跑一遍DAG最小路徑點覆蓋。(這類問題博主更習慣於直接用鄰接矩陣QAQ)
#include <bits/stdc++.h>
#define mem(a, b) memset(a, b, sizeof a)
using namespace std;
const int N = 210;
bool w[N][N];
int match[N];
bool vis[N];
int n, m;
bool dfs(int x){
for (int i = 1; i <= n; i++){
if (w[x][i] && !vis[i]){
vis[i] = 1;
if (match[i] == -1 || dfs(match[i])){
match[i] = x;
return 1;
}
}
}
return 0;
}
void pre_work(){
mem(match, -1);
mem(vis, 0);
for (int k = 1; k <= n; k++){
for (int i = 1; i <= n; i++){
for (int j = 1; j <= n; j++){
w[i][j] = (w[i][k] && w[k][j]) || w[i][j];
}
}
}
}
int solve(){
int ans = n;
for (int i = 1; i <= n; i++){
mem(vis, 0);
if (dfs(i))ans--;
}
return ans;
}
int main()
{
mem(w, 0);
scanf("%d %d", &n, &m);
for (int i = 1; i <= m; i++){
int x, y;
scanf("%d %d", &x, &y);
w[x][y] = 1;
}
pre_work();
printf("%d\n", solve());
return 0;
}