前言

二分图的重点在于建模。以下的题目大家可以清晰的看出来这一点。代码相似度很高,但是思路基本上是各不相同。

题目

HDU 1179 Ollivanders: Makers of Fine Wands since 382 BC.

题意与分析

有n个人要去买魔杖,有m根魔杖(和哈利波特去买魔杖的时候一样,是由魔杖选人)。接下来是m行,每行第一个数k是第i根魔杖可以选的人数,接着k个数表示这根魔杖选的人的编号。最后问老板最多能卖出多少根魔杖。模板题。

代码

/*
* Filename: hdu1179.cpp
* Date: 2018-11-11
*/ #include <bits/stdc++.h> #define INF 0x3f3f3f3f
#define PB emplace_back
#define MP make_pair
#define fi first
#define se second
#define rep(i,a,b) for(repType i=(a); i<=(b); ++i)
#define per(i,a,b) for(repType i=(a); i>=(b); --i)
#define ZERO(x) memset(x, 0, sizeof(x))
#define MS(x,y) memset(x, y, sizeof(x))
#define ALL(x) (x).begin(), (x).end() #define QUICKIO \
ios::sync_with_stdio(false); \
cin.tie(0); \
cout.tie(0);
#define DEBUG(...) fprintf(stderr, __VA_ARGS__), fflush(stderr) using namespace std;
using pi=pair<int,int>;
using repType=int;
using ll=long long;
using ld=long double;
using ull=unsigned long long; const int MAXN=105;
int n,m;
vector<int> G[MAXN];
int linker[MAXN];
bool used[MAXN]; inline void init(int n)
{
rep(i,1,n) G[i].clear();
} bool dfs(int u)
{
rep(v, 0, int(G[u].size())-1)
{
if(!used[G[u][v]])
{
used[G[u][v]]=true;
if(linker[G[u][v]]==-1 || dfs(linker[G[u][v]]))
{
linker[G[u][v]]=u;
return true;
}
}
}
return false;
} inline int hungary(int n)
{
int ret=0;
MS(linker,-1);
rep(u,1,n)
{
ZERO(used);
if(dfs(u)) ret++;
}
return ret;
} int main()
{
while(cin>>m>>n)
{
init(n);
rep(i,1,n)
{
int k; cin>>k;
rep(j,1,k)
{
int tmp; cin>>tmp;
G[i].PB(tmp);
}
}
cout<<hungary(n)<<endl;
} return 0;
}

HDU 1281 棋盘游戏

题意与分析

题意是中文的,不解释了。

因为是车,所以每行每列至多只能放一个棋子。我们可以分别把行和列视作点,那么连接两个集合的一条边就是一个棋子。因此,这个显然的二分图的最大匹配就是我能放的最多棋子数目。那么题意就是枚举重要点,一个个删掉,看看会不会改变最大匹配。

这种行/列的二分匹配是基础套路,望周知。类似的还有矩阵中的\(i+j\)为奇数、偶数的二分集合情况。

代码

/* ACM Code written by Sam X or his teammates.
* Filename: hdu1281.cpp
* Date: 2018-11-14
*/ #include <bits/stdc++.h> #define INF 0x3f3f3f3f
#define PB emplace_back
#define MP make_pair
#define fi first
#define se second
#define rep(i,a,b) for(repType i=(a); i<=(b); ++i)
#define per(i,a,b) for(repType i=(a); i>=(b); --i)
#define ZERO(x) memset(x, 0, sizeof(x))
#define MS(x,y) memset(x, y, sizeof(x))
#define ALL(x) (x).begin(), (x).end() #define QUICKIO \
ios::sync_with_stdio(false); \
cin.tie(0); \
cout.tie(0);
#define DEBUG(...) fprintf(stderr, __VA_ARGS__), fflush(stderr) using namespace std;
using pi=pair<int,int>;
using repType=int;
using ll=long long;
using ld=long double;
using ull=unsigned long long; const int MAXN=105;
struct Edge
{
int u,v;
bool is_ok;
Edge() {}
Edge(int _u, int _v):u(_u), v(_v), is_ok(true) {}
};
vector<Edge> edges;
vector<int> G[MAXN];
void add_edge(int u, int v)
{
edges.PB(u,v);
G[u].PB(int(edges.size())-1);
} int linker[MAXN];
bool used[MAXN];
bool dfs(int u)
{
rep(i,0,int(G[u].size())-1)
{
int v=edges[G[u][i]].v;
if(!edges[G[u][i]].is_ok) continue;
if(!used[v])
{
used[v]=true;
if(linker[v]==-1 || dfs(linker[v]))
{
linker[v]=u;
return true;
}
}
}
return false;
} int hungary(int n)
{
int ret=0;
MS(linker, -1);
rep(u,1,n)
{
ZERO(used);
if(dfs(u)) ret++;
}
return ret;
} int main()
{
int n,m,k,kase=0;
while(cin>>n>>m>>k)
{
rep(i,1,n) G[i].clear();
rep(i,1,k)
{
int x,y; cin>>x>>y;
add_edge(x,y);
}
int max_match=hungary(n),important_cnt=0;
rep(i,0,int(edges.size())-1)
{
edges[i].is_ok=false;
if(hungary(n)!=max_match)
{
important_cnt++;
}
edges[i].is_ok=true;
}
cout<<"Board "<<++kase<<" have "<<important_cnt<<" important blanks for "<<max_match<<" chessmen."<<endl;
}
return 0;
}

HDU 1498 50 years, 50 colors

题意与代码

题意是这样的:给定一个气球矩阵,每次只能消除一行或一列的相同颜色的气球,求有多少种气球在k次内不能消除。

这题看起来和二分图没啥关系,但是这就是二分图题目的魅力所在了:建模是大头。我们先考虑单个颜色,因为要最小次数打掉所有单个颜色,而我们每次只能打掉同一行/列的——问题于是转化成了用最少的行/列来打掉所有气球:这就是二分图的最小顶点覆盖问题。建模方法和上一题差不多:将行和列二分图的两个顶点集合,原图中的每个点相当于二分图中的边然后分别枚举每种颜色并判断就可以了。

对这题没啥头绪的再看看我的这篇博客:https://www.cnblogs.com/samhx/p/HDU-1150.html 。它对最小顶点覆盖有了一个比较好的讲解,并且也讲了下增广路的扩张过程。

代码

虽然思路是这样的,但是代码中仍然有一些实现上的细节。

/* ACM Code written by Sam X or his teammates.
* Filename: hdu1498.cpp
* Date: 2018-11-16
*/ #include <bits/stdc++.h> #define INF 0x3f3f3f3f
#define PB emplace_back
#define MP make_pair
#define fi first
#define se second
#define rep(i,a,b) for(repType i=(a); i<=(b); ++i)
#define per(i,a,b) for(repType i=(a); i>=(b); --i)
#define ZERO(x) memset(x, 0, sizeof(x))
#define MS(x,y) memset(x, y, sizeof(x))
#define ALL(x) (x).begin(), (x).end() #define QUICKIO \
ios::sync_with_stdio(false); \
cin.tie(0); \
cout.tie(0);
#define DEBUG(...) fprintf(stderr, __VA_ARGS__), fflush(stderr) using namespace std;
using pi=pair<int,int>;
using repType=int;
using ll=long long;
using ld=long double;
using ull=unsigned long long; const int MAXN=105;
struct Edge
{
int u,v;
int id;
Edge() {}
Edge(int _u, int _v, int _i):u(_u), v(_v), id(_i) {}
};
vector<Edge> edges;
vector<int> G[MAXN];
void add_edge(int u, int v,int id)
{
edges.PB(u,v,id);
G[u].PB(int(edges.size())-1);
} int now_id=-1;
int linker[MAXN];
bool used[MAXN];
bool dfs(int u)
{
rep(i,0,int(G[u].size())-1)
{
int v=edges[G[u][i]].v;
if(edges[G[u][i]].id!=now_id) continue;
if(!used[v])
{
used[v]=true;
if(linker[v]==-1 || dfs(linker[v]))
{
linker[v]=u;
return true;
}
}
}
return false;
} int hungary(int n)
{
int ret=0;
MS(linker, -1);
rep(u,1,n)
{
ZERO(used);
if(dfs(u)) ret++;
}
return ret;
} unordered_map<int,int> hsm;
vector<int> hsv;
int get_hash(int x)
{
if(hsm.find(x)==hsm.end())
{
hsv.PB(x);
return hsm[x]=hsv.size()-1;
}
else return hsm[x];
} int main()
{
int n,max_cnt;
while(cin>>n>>max_cnt)
{
hsm.clear();
hsv.clear();
edges.clear();
if(!n && !max_cnt) break;
rep(i,1,n) G[i].clear();
rep(i,1,n)
{
rep(j,1,n)
{
int tmp; cin>>tmp;
add_edge(i,j,get_hash(tmp));
}
}
vector<int> ans;
for(now_id=0;now_id<hsv.size();++now_id)
{
if(hungary(n)>max_cnt)
{
ans.PB(hsv[now_id]);
}
}
if(ans.size()==0) cout<<-1<<endl;
else
{
sort(ALL(ans));
rep(i,0,ans.size()-1)
cout<<ans[i]<<char(i==ans.size()-1?'\n':' ');
}
}
return 0;
}

「日常温习」Hungary算法解决二分图相关问题的更多相关文章

  1. LOJ #2540. 「PKUWC 2018」随机算法(概率dp)

    题意 LOJ #2540. 「PKUWC 2018」随机算法 题解 朴素的就是 \(O(n3^n)\) dp 写了一下有 \(50pts\) ... 大概就是每个点有三个状态 , 考虑了但不在独立集中 ...

  2. [转帖]「日常小记」linux中强大且常用命令:find、grep

    「日常小记」linux中强大且常用命令:find.grep https://zhuanlan.zhihu.com/p/74379265 在linux下面工作,有些命令能够大大提高效率.本文就向大家介绍 ...

  3. 「日常训练」Girls and Boys(HDU-1068)

    题意 有n个同学,给出同学之间的爱慕关系,选出一个集合使得集合中的人没有爱慕关系.问能选出的最大集合是多少. 分析 二分图的最大独立集. 最大独立集的意思是,在图中选出最多的点,使他们两两之间没有边, ...

  4. Kuhn-Munkras算法解决二分图最优权值匹配

    在看这篇博文之前建议看一下上一篇匈牙利法解决二分图最大匹配问题: https://www.cnblogs.com/fangxiaoqi/p/10808729.html 这篇博文参考自:https:// ...

  5. 「日常训练」Mike and Feet(Codeforces Round #305 Div. 2 D)

    题意 (Codeforces 548D) 对一个有$n$个数的数列,我们要求其连续$x(1\le x\le n)$(对于每个$x$,这样的连续group有若干个)的最小数的最大值. 分析 这是一道用了 ...

  6. 「日常训练」Card Game Cheater(HDU-1528)

    题意与分析 题意是这样的:有\(n\)张牌,然后第一行是Adam的牌,第二行是Eve的牌:每两个字符代表一张牌,第一个字符表示牌的点数,第二个表示牌的花色.Adam和Eve每次从自己的牌中选出一张牌进 ...

  7. 「日常训练」Uncle Tom's Inherited Land*(HDU-1507)

    题意与分析 题意是这样的:给你一个\(N\times M\)的图,其中有一些点不能放置\(1\times 2\)大小的矩形,矩形可以横着放可以竖着放,问剩下的格子中,最多能够放多少个矩形. 注意到是\ ...

  8. 「日常训练」Common Subexpression Elimination(UVa-12219)

    今天做的题目就是抱佛脚2333 懂的都懂. 这条题目干了好几天,最后还是参考别人的代码敲出来了,但是自己独立思考了两天多,还是有收获的. 思路分析 做这条题我是先按照之前的那条题目(The SetSt ...

  9. 「日常开发」记一次因使用Date引起的线上BUG处理

    生活中,我们需要掌控自己的时间,减少加班,提高效率:日常开发中,我们需要操作时间API,保证效率.安全.稳定.现在都2020年了,了解如何在JDK8及以后的版本中更好地操控时间就很有必要,尤其是一次线 ...

随机推荐

  1. C/C++心得-面向对象

    首先本文以C++描述面向对象.面向对象应该可以说是C++对C最为重要的扩充.面向对象使得C++可以用更符合人的思维模式的方式编程,使得有一定基础的程序员可以更容易的写程序.相对于C,C++还有其他许多 ...

  2. php------中文出现乱码解决方法

    中文出现乱码解决方法:原因编码格式不一致 [1]  建立数据库的时候,字符集选择utf-8  数据库,点表名,点右键,数据库属性…. [2]  修改myspl的配置,在[myspld]模块下面添加ch ...

  3. Kali-linux基本设置

    1.6.1 启动默认的服务 Kali Linux自带了几个网络服务,它们是非常有用的.但是默认是禁用的.在这里,将介绍使用各种方法设置并启动每个服务. 1. 启动Apache服务 启动Apache服务 ...

  4. sql的寫法,推薦的寫法,全文索引提高類似like查詢的效率

    說明:logistics_detail表中每個deliver_id可能對應多個loginticsType,但是我們只要獲取最大的那一個. SELECT dm.delivery_code,dm.deli ...

  5. mac无法使用80端口问题

    前言:在mac os中,非root用户是无法使用小于1024的常用端口的.如果开发中需要用到80端口, 就要设置端口转发. hosts文件介绍(1)hosts文件是将域名和IP地址建立映射关系的系统文 ...

  6. 简单使用idea Spring Boot搭建项目

    第一步:使用Spring Initializr创建 第二步:项目配置 第三步:选择项目需要的依赖 第五步: ok 创建完成,修改仓库 maven{ url 'http://maven.aliyun.c ...

  7. 【洛谷P1373】小a和uim之大逃离

    小a和uim之大逃离 题目链接 因为每次只能向下或向右走,我们可以递推 dp[i][j][d][0/1]表示走到(i,j),mod k 意义下差值为d,轮到小a/小uim操作时的方案数 dp[i][j ...

  8. 【SQLSERVER学习笔记】分页存储过程+调用

    USE [数据库名] GO SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE PROCEDURE [dbo].[存储过程名] @pageI ...

  9. asp.net mvc 如何在View中获取Url参数的值

    如果url是 /home/index?id=3 直接Request就ok. 但是如果路由设定为:{controller}/{action}/{id} url是 /home/index/3   这时想在 ...

  10. SignalR 教程二 服务端广播

    转帖官方教程:Tutorial: Server Broadcast with SignalR 2 http://www.asp.net/signalr/overview/getting-started ...