1. 程式人生 > >Antenna Placement POJ - 3020 二分圖匹配 匈牙利 拆點建圖 最小路徑覆蓋

Antenna Placement POJ - 3020 二分圖匹配 匈牙利 拆點建圖 最小路徑覆蓋

har 一個 article main 左右 int 集合 || sin

題意:圖沒什麽用 給出一個地圖 地圖上有 點 一次可以覆蓋2個連續 的點( 左右 或者 上下表示連續)問最少幾條邊可以使得每個點都被覆蓋

最小路徑覆蓋 最小路徑覆蓋=|G|-最大匹配數 證明:https://blog.csdn.net/qq_34564984/article/details/52778763

證明總的來說就是盡可能多得連邊 邊越多 可以打包一起處理得點就越多(這裏題中打包指連續得兩個點只需要一條線段就能覆蓋)

拆點思想 :匈牙利拆了點才好寫 不然十分麻煩 簡單地說就是點復制一遍 從一邊開始匹配

建圖:X如果需要覆蓋 和它上下左右需要覆蓋的點連邊 當然這裏是和拆完點的另外一個部分的點連邊 amp[x][y]兩維 分別表示兩個集合

答案 最小路徑覆蓋 = 頂點數 – 最大二分匹配數/2 為什麽要除以2呢,因為拆點復制了一遍 需要除回去 比如

1 2 有變 變成

1 和2‘

2 和 1‘形成了匹配 這樣匹配就加倍了 所以除以2就好

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=3000;
char mp[maxn][maxn];
int  amp[maxn][maxn];
int vis[maxn];
int Hash[maxn][maxn];
int cnt=0; int ans=0; int link[maxn]; int dx[]={ 1,-1,0,0 }; int dy[]={ 0,0,-1,1 }; bool dfs(int x){ for(int i=1;i<=cnt;i++){ if(amp[x][i]&&!vis[i]){ vis[i]=1; if(link[i]==0||dfs(link[i])){ link[i]=x; return 1; } } }
return 0; } void solve(){ ans=0; memset(link,0,sizeof(link)); for(int i=1;i<=cnt;i++){ memset(vis,0,sizeof(vis)); if(dfs(i))ans++; } } int main(){ int t; cin>>t; while(t--){ int n,m; cnt=0; memset(amp,0,sizeof(amp)); cin>>n>>m; for(int i=1;i<=n;i++)scanf("%s",mp[i]+1); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(mp[i][j]==*) Hash[i][j]=++cnt; } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(mp[i][j]==*){ for(int k=0;k<4;k++){ int tx=i+dx[k],ty=j+dy[k]; if(mp[tx][ty]==*)amp[Hash[i][j]][Hash[tx][ty]]=1; } } } } solve(); cout<<cnt-ans/2<<endl; } return 0; }

Antenna Placement POJ - 3020 二分圖匹配 匈牙利 拆點建圖 最小路徑覆蓋