關於二分匹配的幾道簡單題
最近重新學習了二分匹配的演算法,並趁熱打鐵做了幾道簡單的題。
把每一個任務在兩種機器下的模式看作一條邊,建圖之後求最小頂點覆蓋集(用最少的點,使每一條邊都與其中一個點相關聯),而最小頂點覆蓋集=最大匹配,此題在輸入的時候把模式為0的去掉,因為兩種機器初始狀態為0。
#include <stdio.h> #include <string.h> const int maxn = 110; int G[maxn][maxn],vis[maxn],match[maxn]; int nx,ny,m; bool find(int u) { for(int i = 0; i < ny; i++){ if(G[u][i] && !vis[i]){ vis[i] = 1; if(match[i] == -1 || find(match[i])){ match[i] = u; return true; } } } return false; } int max_match() { int ans = 0; memset(match,-1,sizeof(match)); for(int i = 0; i < nx; i++){ memset(vis,0,sizeof(vis)); if(find(i)){ ans++; } } return ans; } int main(void) { while(scanf("%d",&nx) != EOF && nx){ scanf("%d%d",&ny,&m); memset(G,0,sizeof(G)); for(int i = 1; i <= m; i++){ int id,x,y; scanf("%d%d%d",&id,&x,&y); if(x && y){ G[x][y] = 1; } } printf("%d\n",max_match()); } return 0; }
給出一個有向圖,士兵沿著路的方向走,問至少需要多少士兵,能把所有邊都走一遍。其實就是求有向無環圖的最小路徑覆蓋,有向無環圖的最小路徑覆蓋=頂點數-最大匹配數
#include <stdio.h> #include <string.h> const int maxn = 150; int match[maxn],G[maxn][maxn],vis[maxn]; int n,m; bool find(int u) { for(int i = 1; i <= n; i++){ if(G[u][i] && !vis[i]){ vis[i] = 1; if(match[i] == -1 || find(match[i])){ match[i] = u; return true; } } } return false; } int max_match() { int ans = 0; memset(match,-1,sizeof(match)); for(int i = 1; i <= n; i++){ memset(vis,0,sizeof(vis)); if(find(i)){ ans++; } } return ans; } int main(void) { int T; scanf("%d",&T); while(T--){ scanf("%d%d",&n,&m); memset(G,0,sizeof(G)); for(int i = 1; i <= m; i++){ int a,b; scanf("%d%d",&a,&b); G[a][b] = 1; } printf("%d\n",n - max_match()); } return 0; }
找出一個最大的集合,使這個集合的任意兩個同學都沒有浪漫的關係。求的是最大獨立集(在n個點的圖中,選出一個最大點集m,並且這m個點任意兩點之間都沒有邊相連),最大獨立集=頂點數-最大匹配數,這道題並沒有指定性別,所以要減去最大匹配/2。
#include <stdio.h> #include <string.h> const int maxn = 1005; int match[maxn],G[maxn][maxn],vis[maxn]; int n,m; bool find(int u) { for(int i = 0; i < n; i++){ if(G[u][i] && !vis[i]){ vis[i] = 1; if(match[i] == -1 || find(match[i])){ match[i] = u; return true; } } } return false; } int max_match() { int ans = 0; memset(match,-1,sizeof(match)); for(int i = 0; i < n; i++){ memset(vis,0,sizeof(vis)); if(find(i)){ ans++; } } return ans; } int main(void) { while(scanf("%d",&n) != EOF){ memset(G,0,sizeof(G)); int id,num; for(int i = 0; i < n; i++){ scanf("%d: (%d)",&id,&num); while(num--){ scanf("%d",&id); G[i][id] = 1; } } printf("%d\n",n - max_match() / 2); } return 0; }
對於一個點(i,j),如果放上車,就在i,j之間連上一條邊,放的車的最大個數就是最大匹配數,先求出最大匹配數,然後列舉每一個點,求出去掉這個點之後的最大匹配數,如果比之前的少,說明是重要點。
#include <stdio.h>
#include <string.h>
int match[105];
int x[10005],y[10005];
int n,m,k;
int vis[105];
int a[105][105];
int b[105][105];
int find(int u){
for(int i=1;i<=m;i++){
if(vis[i] == 0 && a[u][i] && !b[u][i]){
vis[i] = 1;
if(match[i] == 0 || find(match[i])){
match[i] = u;
return 1;
}
}
}
return 0;
}
int main(void){
int count = 0;
while(scanf("%d%d%d",&n,&m,&k)!=EOF){
memset(match,0,sizeof(match));
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
for(int i=1;i<=k;i++){
scanf("%d%d",&x[i],&y[i]);
a[x[i]][y[i]] = 1;
}
int ans=0;
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
if(find(i)){
ans++;
}
}
int anss = 0;
for(int i=1;i<=k;i++){
b[x[i]][y[i]] = 1;
memset(match,0,sizeof(match));
int temp=0;
for(int j=1;j<=n;j++){
memset(vis,0,sizeof(vis));
if(find(j)){
temp++;
}
}
if(temp < ans){
anss++;
}
b[x[i]][y[i]] = 0;
}
printf("Board %d have %d important blanks for %d chessmen.\n",++count,anss,ans);
}
return 0;
}
給出一個矩陣,每一個數代表一種氣球,求有多少種氣球,每次刷掉一行或一列,在最多刷k次的情況下刷不完,按字典序輸出,不存在則輸出-1。還是根據行列座標建圖,並且圖裡儲存的是哪種氣球,選出最少的行號或列號覆蓋所有儲存這種顏色的所有邊,最小點覆蓋集,即求最大匹配,然後與k比較。
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int maxn = 105;
int ans[maxn];
int match[maxn],G[maxn][maxn],vis[maxn],used[maxn];
int n,c,k;
bool find(int u)
{
for(int i = 1; i <= n; i++){
if(G[u][i] == c && !vis[i]){
vis[i] = 1;
if(match[i] == -1 || find(match[i])){
match[i] = u;
return true;
}
}
}
return false;
}
int max_match()
{
int ans = 0;
memset(match,-1,sizeof(match));
for(int i = 1; i <= n; i++){
memset(vis,0,sizeof(vis));
if(find(i)){
ans++;
}
}
return ans;
}
int main(void)
{
while(scanf("%d%d",&n,&k) != EOF){
if(n == 0 && k == 0){
break;
}
memset(G,0,sizeof(G));
memset(used,0,sizeof(used));
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
scanf("%d",&G[i][j]);
}
}
int cnt = 0;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
if(!used[G[i][j]]){
used[G[i][j]] = 1;
c = G[i][j];
int num = max_match();
if(num > k){
ans[cnt++] = c;
}
}
}
}
if(cnt == 0){
printf("-1\n");
}
else{
sort(ans,ans + cnt);
for(int i = 0; i < cnt; i++){
if(i == cnt - 1){
printf("%d\n",ans[i]);
}
else{
printf("%d ",ans[i]);
}
}
}
}
return 0;
}
很明顯的求最大匹配,就是需要判斷一下兩張牌的大小關係。
#include <stdio.h>
#include <string.h>
#include <map>
using namespace std;
char a[50][2],b[50][2];
int match[50],vis[50];
int n;
map <char,int> mp;
void init()
{
for(char i = '2'; i <= '9'; i++){
mp[i] = i - '0';
}
mp['T'] = 10;
mp['J'] = 11;
mp['Q'] = 12;
mp['K'] = 13;
mp['A'] = 14;
mp['H'] = 3;
mp['S'] = 2;
mp['D'] = 1;
mp['C'] = 0;
}
bool cmp(int i,int j)
{
int ansb = mp[b[i][0]];
int ansa = mp[a[j][0]];
if(ansa == ansb){
ansb += mp[b[i][1]];
ansa += mp[a[j][1]];
}
else{
return ansb > ansa;
}
return ansb > ansa;
}
bool find(int u)
{
for(int i = 1; i <= n; i++){
if(cmp(u,i) && !vis[i]){
vis[i] = 1;
if(match[i] == -1 || find(match[i])){
match[i] = u;
return true;
}
}
}
return false;
}
int max_match()
{
int ans = 0;
memset(match,-1,sizeof(match));
for(int i = 1; i <= n; i++){
memset(vis,0,sizeof(vis));
if(find(i)){
ans++;
}
}
return ans++;
}
int main(void)
{
int T;
init();
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(int i = 1; i <= n; i++){
scanf("%s",a[i]);
}
for(int i = 1; i <= n; i++){
scanf("%s",b[i]);
}
printf("%d\n",max_match());
}
return 0;
}
這道題建出二分圖是關鍵,先給每個空白點編號,然後橫縱座標相加為奇數的空白點為X集合,橫縱座標相加為偶數的的空白點為Y集合,如果兩個點,一個屬於X,一個屬於Y,並且這兩個點上或下或左或右相鄰,兩個點連線一條邊,這樣就構建除了二分圖,求最大匹配即可,如何打印出結果看程式碼。
#include <stdio.h>
#include <string.h>
const int maxn = 105;
int G[maxn][maxn],vis[maxn];
int bin[maxn][maxn],match[maxn];
int num[maxn][maxn];
int node[maxn][2];
int n,m;
int to[4][2] = {0,1,1,0,-1,0,0,-1};
int cnt;
bool find(int u)
{
for(int i = 1; i < cnt; i++){
if(bin[u][i] && !vis[i]){
vis[i] = 1;
if(match[i] == -1 || find(match[i])){
match[i] = u;
return true;
}
}
}
return false;
}
int max_match()
{
int ans = 0;
memset(match,-1,sizeof(match));
for(int i = 1; i < cnt; i++){
memset(vis,0,sizeof(vis));
if(find(i)){
ans++;
}
}
return ans;
}
int main(void)
{
while(scanf("%d%d",&n,&m) != EOF){
if(n + m == 0){
break;
}
int k;
scanf("%d",&k);
memset(G,0,sizeof(G));
memset(bin,0,sizeof(bin));
memset(node,0,sizeof(node));
memset(num,0,sizeof(num));
for(int i = 1; i <= k; i++){
int a,b;
scanf("%d%d",&a,&b);
G[a][b] = 1;
}
cnt = 1;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if(!G[i][j]){
node[cnt][0] = i;
node[cnt][1] = j;
num[i][j] = cnt++;
}
}
}
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if((i + j) & 1 && num[i][j]){
for(int q = 0; q <= 3; q++){
int x = i + to[q][0];
int y = j + to[q][1];
if(x >= 1 && y >= 1 && x <= n && y <= m && num[x][y]){
//printf("%d___%d\n",num[i][j],num[x][y]);
bin[num[i][j]][num[x][y]] = 1;
}
}
}
}
}
printf("%d\n",max_match());
for(int i = 1; i < cnt; i++){
if(match[i] != -1){
printf("(%d,%d)--(%d,%d)\n",node[i][0],node[i][1],node[match[i]][0],node[match[i]][1]);
}
}
printf("\n");
}
return 0;
}
因為*同時消毒兩次,所以儘量用*,將所有操作解碼,用一個vis陣列標記,對於二進位制只有一位不同的兩個點連線一條邊,求最大獨立集,注意這個圖是有向圖,因為兩個只差一位的數合併成一個,建圖時是遍歷所有的點,一個點用完後和它連線的點會再次出現,而我們要求只匹配一次即可。
#include <stdio.h>
#include <string.h>
#include <vector>
using namespace std;
const int maxn = 1 << 11;
int vis[maxn],used[maxn];
int match[maxn],n,m,cnt;
vector<int> G[maxn];
void input(){
for(int i = 0; i < (1 << n); i++){
G[i].clear();
vis[i] = 0;
}
int flag;
char op[15];
int val;
for(int i = 0; i < m; i++){
flag = -1;
val = 0;
scanf("%s",op);
for(int j = 0; j < n; j++){
if(op[j] == '*'){
flag = j;
}
else if(op[j] == '1'){
val |= (1 << j);
}
}
vis[val] = 1;
if(flag != -1){
val |= (1 << flag);
vis[val] = 1;
}
}
cnt = 0;
for(int i = 0; i < (1 << n); i++){
if(vis[i]){
cnt++;
for(int j = 0; j < (1 << n); j++){
if(vis[j]){
int temp = i ^ j;
if(temp && !((temp) & (temp - 1))){
G[i].push_back(j);
G[j].push_back(i);
}
}
}
}
}
}
bool find(int i){
for(int j = 0; j < G[i].size(); j++){
int v = G[i][j];
if(!used[v]){
used[v] = 1;
if(match[v] == -1 || find(match[v])){
match[v] = i;
return true;
}
}
}
return false;
}
int max_match(){
memset(match,-1,sizeof(match));
int ans = 0;
for(int i = 0; i < (1 << n); i++){
if(vis[i]){
memset(used,0,sizeof(used));
if(find(i)){
ans++;
}
}
}
return ans;
}
int main(void){
while(scanf("%d%d",&n,&m) != EOF && (n + m)){
input();
printf("%d\n",cnt - max_match() / 2);
}
return 0;
}
先對任意兩條街道求最短路,用floyd就行,然後建圖,兩個任務i,j,如果完成i之後在到達j時的時間小於最晚到達j的時間,則i與j之間加一條邊,求最小路徑覆蓋即可。
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
int dis[25][25];
int G[205][205],match[205],vis[205];
int p[205],t[205],d[205];
int Q,M;
void floyd()
{
for(int k = 1; k <= Q; k++){
for(int i = 1; i <= Q; i++){
for(int j = 1; j <= Q; j++){
if(dis[i][j] > dis[i][k] + dis[k][j]){
dis[i][j] = dis[i][k] + dis[k][j];
}
}
}
}
}
void input()
{
for(int i = 1; i <= Q; i++){
for(int j = 1; j <= Q; j++){
scanf("%d",&dis[i][j]);
if(dis[i][j] == -1){
dis[i][j] = INF;
}
}
}
for(int i = 1; i <= M; i++){
scanf("%d%d%d",&p[i],&t[i],&d[i]);
}
}
void get_map()
{
for(int i = 1; i <= M; i++){
for(int j = 1; j <= M; j++){
if(t[i] + dis[p[i]][p[j]] + d[i] <= t[j]){
G[i][j] = 1;
}
}
}
}
void init()
{
for(int i = 1; i <= M; i++){
for(int j = 1; j <= M; j++){
G[i][j] = 0;
}
}
}
bool find(int u)
{
for(int i = 1; i <= M; i++){
if(G[u][i] && !vis[i]){
vis[i] = 1;
if(match[i] == -1 || find(match[i])){
match[i] = u;
return true;
}
}
}
return false;
}
int max_match()
{
int ans = 0;
memset(match,-1,sizeof(match));
for(int i = 1; i <= M; i++){
memset(vis,0,sizeof(vis));
if(find(i)){
ans++;
}
}
return ans;
}
int main(void)
{
while(scanf("%d%d",&Q,&M) && (Q + M)){
init();
input();
floyd();
get_map();
printf("%d\n",M - max_match());
}
return 0;
}