「日常温习」Hungary算法解决二分图相关问题
前言
二分图的重点在于建模。以下的题目大家可以清晰的看出来这一点。代码相似度很高,但是思路基本上是各不相同。
题目
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算法解决二分图相关问题的更多相关文章
- LOJ #2540. 「PKUWC 2018」随机算法(概率dp)
题意 LOJ #2540. 「PKUWC 2018」随机算法 题解 朴素的就是 \(O(n3^n)\) dp 写了一下有 \(50pts\) ... 大概就是每个点有三个状态 , 考虑了但不在独立集中 ...
- [转帖]「日常小记」linux中强大且常用命令:find、grep
「日常小记」linux中强大且常用命令:find.grep https://zhuanlan.zhihu.com/p/74379265 在linux下面工作,有些命令能够大大提高效率.本文就向大家介绍 ...
- 「日常训练」Girls and Boys(HDU-1068)
题意 有n个同学,给出同学之间的爱慕关系,选出一个集合使得集合中的人没有爱慕关系.问能选出的最大集合是多少. 分析 二分图的最大独立集. 最大独立集的意思是,在图中选出最多的点,使他们两两之间没有边, ...
- Kuhn-Munkras算法解决二分图最优权值匹配
在看这篇博文之前建议看一下上一篇匈牙利法解决二分图最大匹配问题: https://www.cnblogs.com/fangxiaoqi/p/10808729.html 这篇博文参考自:https:// ...
- 「日常训练」Mike and Feet(Codeforces Round #305 Div. 2 D)
题意 (Codeforces 548D) 对一个有$n$个数的数列,我们要求其连续$x(1\le x\le n)$(对于每个$x$,这样的连续group有若干个)的最小数的最大值. 分析 这是一道用了 ...
- 「日常训练」Card Game Cheater(HDU-1528)
题意与分析 题意是这样的:有\(n\)张牌,然后第一行是Adam的牌,第二行是Eve的牌:每两个字符代表一张牌,第一个字符表示牌的点数,第二个表示牌的花色.Adam和Eve每次从自己的牌中选出一张牌进 ...
- 「日常训练」Uncle Tom's Inherited Land*(HDU-1507)
题意与分析 题意是这样的:给你一个\(N\times M\)的图,其中有一些点不能放置\(1\times 2\)大小的矩形,矩形可以横着放可以竖着放,问剩下的格子中,最多能够放多少个矩形. 注意到是\ ...
- 「日常训练」Common Subexpression Elimination(UVa-12219)
今天做的题目就是抱佛脚2333 懂的都懂. 这条题目干了好几天,最后还是参考别人的代码敲出来了,但是自己独立思考了两天多,还是有收获的. 思路分析 做这条题我是先按照之前的那条题目(The SetSt ...
- 「日常开发」记一次因使用Date引起的线上BUG处理
生活中,我们需要掌控自己的时间,减少加班,提高效率:日常开发中,我们需要操作时间API,保证效率.安全.稳定.现在都2020年了,了解如何在JDK8及以后的版本中更好地操控时间就很有必要,尤其是一次线 ...
随机推荐
- C/C++心得-面向对象
首先本文以C++描述面向对象.面向对象应该可以说是C++对C最为重要的扩充.面向对象使得C++可以用更符合人的思维模式的方式编程,使得有一定基础的程序员可以更容易的写程序.相对于C,C++还有其他许多 ...
- Mybatis 和Spring整合之mapper代理开发
F:\1ziliao\mybatis\代码 1.1 SqlMapConfig.xml <?xml version="1.0" encoding="UTF-8&quo ...
- Android客户端与服务器端通过DES加密认证
转载地址:http://blog.csdn.net/spring21st/article/details/6730283 由于Android应用没有像web开发中的session机制,所以采用PHPS ...
- Google Guava -缓存cache简单使用
package guavacache; import java.util.concurrent.ExecutionException; import java.util.concurrent.Time ...
- (二、下) springBoot 、maven 、mysql、 mybatis、 通用Mapper、lombok 简单搭建例子 《附项目源码》
接着上篇文章中 继续前进. 一.在maven 的pom.xm中添加组件依赖, mybatis通用Mapper,及分页插件 1.mybatis通用Mapper <!-- mybatis通用Mapp ...
- Cannot set HTTP gem source: “source https://rubygems.org not present in cache”
My ruby version in Windows 10: > ruby -v ruby 2.3.1p112 (2016-04-26 revision 54768) [i386-mingw32 ...
- window.moveTo(),window.moveBy()不生效
window.moveTo()和window.moveBy的菜鸟教程介绍: moveTo() 方法可把窗口的左上角移动到一个指定的坐标. window.moveTo(x,y) moveBy() 方法可 ...
- 结合Bootbox将后台JSON数据填充Form表单
本文介绍了如何结合Bootbox将后台JSON数据填充到Form表单中,同时也介绍了一些需要使用的知识的学习途径,并附上了参考文档地址与学习网址,对此感兴趣的伙伴可以直接访问学习.为了方便介绍,使用了 ...
- linux服务器项目部署【完整版】
之前总玩v8虚拟机,最近看到腾讯云学生套餐很实惠就租了个linux服务器搭一个项目,做下这个项目部署全记录,即为了方便以后查看,同时也分享下自己的经验,不足之处还请多多指教,废话不多说,直接开始!!! ...
- s3c2440中断控制器操作
一.ARM中断体系结构 arm有7中异常工作模式 用户模式.快中断模式.管理模式.数据访问终止模式.中断模式.系统模式.未定义指令终止模式. 几种模式有什么不同呢, 1.不同的寄存器 2.不同的权限 ...