前言

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

题目

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. 动态截屏软件jpg格式

    软件下载地址:https://github.com/weibanggang/jiedu 开始截屏 保存路径 生成图片 预览

  2. HDU 2092 (将表达式变成一元二次方程形式)

    传送门: http://acm.hdu.edu.cn/showproblem.php?pid=2092 整数解 Time Limit: 1000/1000 MS (Java/Others)    Me ...

  3. IOS开发,摄像头对焦状态监控

    camera autofocus observer? I find the solution for my case to find when autofocus starts / ends. It' ...

  4. Oracle常用内置函数

    转换函数 to_char(d|n,fmt):把日期和数字转换为指定格式的字符串: to_number(x,fmt):把一个字符串转换为一个指定格式的数字:   判空函数 nvl(x,value):如果 ...

  5. 快速排序_c++

    快速排序_c++ GitHub 文解 快速排序正如其名,是一种排序速度较快的排序算法. 其核心思想: 取数组的第一个数,确定其在整个数组中的位置. 以刚刚的数值所确定的位置经数组分为两个部分. 再分别 ...

  6. Swift_销毁

    Swift_销毁 点击查看源码 销毁 func test() { class SomeClass { //类销毁时 通知此方法 deinit { print("销毁") } } v ...

  7. Swiper2和Swiper3区别详解与兼容IE8/IE9

    最近项目一些网站项目想到用Swiper3来制作响应式,但是发现IE9都不兼容, 而swiper2版本又少一个breakpoints参数 做响应式脚本非常不方便,于是想到新版的浏览器用3  ,iE9和以 ...

  8. acm--1006

    Problem Description The three hands of the clock are rotating every second and meeting each other ma ...

  9. Zabbix——解决中文显示乱码

    前提条件: 准备好上传工具,我用的是WinSCP 使用字体是微软雅黑,如果使用其他的更改名称即可. 更改Zabbix服务器的默认参数: vi /usr/share/zabbix/include/def ...

  10. MyEclipse中好用的快捷键汇总整理

    MyEclipse中常用的快捷键有很多,合理的使用其中一些快捷键组合,可以有效提高开发的效率和质量. 1.Ctrl + Shift + R:打开资源.可以查找并打开工作区中任何一个文件,且支持使用通配 ...