前言

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

题目

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. vector size函数使用注意事项

    vector 的size函数返回vector大小,返回值类型为size_type,Member type size_type is an unsigned integral type,即无符号整数: ...

  2. 图片验证码——base64编码的使用

    一.介绍: 1.base64编码简介: Base64就是一种编码格式.Base64要求把每三个8Bit的字节转换为四个6Bit的字节(3*8 = 4*6 = 24),然后把6Bit再添两位高位0,组成 ...

  3. Spring(十七)之表单处理

    表单处理在实际开发中,非常常见,比如登录.注册或者新增.修改等等. 希望本示例对于初学者有一定的提升和帮助 该表单实例,主要说明MVC,相当于前台表单提交,提交相当于一个Http请求,这个请求通过Co ...

  4. Kernel Ridge Regression

    回顾一下岭回归,岭回归的目的是学习得到特征和因变量之间的映射关系,由于特征可能很高维,所以需要正则化 岭回归的目标函数是 $$ \sum_{i=1}^n \left\|y-X\beta\right\| ...

  5. ASP.NET Core MVC的路由参数中:exists后缀有什么作用,顺便谈谈路由匹配机制

    我们在ASP.NET Core MVC中如果要启用Area功能,那么会看到在Startup类的Configure方法中是这么定义Area的路由的: app.UseMvc(routes => { ...

  6. 【转载】RETE算法研究

    本文转自:http://www.ibm.com/developerworks/cn/opensource/os-drools/ RETE算法是大多数规则引擎采用的一种模式匹配算法,比如开源的Drool ...

  7. iOS:SQL

    iOS虽然也有SQL,不过用得少(至少我目前是这样).大数据直接丢给后台,小的用Plist足矣. 再退一步,有FMDB,原生的也用得少了. 下面是之前学SQL时候的笔记. 1.创建 1-1).打开:  ...

  8. 我的前端工具集(七)div背景网格

    我的前端工具集(七)div背景网格   liuyuhang原创,未经允许禁止转载 目录 我的前端工具集 有时候总觉得div颜色过于白,于是给了10%的灰 但是并不一定能解决问题,因为页面中会有不均衡的 ...

  9. React通过dva-model-extend实现 dva 动态生成 model

    前言 实现通过单个component 单个router通过相应的标识对应产生不同model实现数据包分离,model namespce将会覆盖基础的Model,其中的model[state|subsc ...

  10. activemq整合springboot使用(个人微信小程序用)

    1.引入依赖 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spri ...