二分图的定义:如果一张无向图中的所有节点能分成两个集合,使得每个集合内部的点两两之间没有连边,那么此图就是一张二分图。
一.判定
当且仅当一张无向图中不存在边长为奇数的环时,这张图即为二分图(大家可以手动画个奇环来看一看感性理解一下,严密的数学证明大家自己找一下吧 不过貌似别的博客也这么说 )。
所以我们可以用染色大法来判断一张图是否为二分图。
走个代码:
inline void dfs(int u,int color){
vis[u]=color;
for(int i=head[u];i;i=e[i].nt){
int v=e[i].v;
if(!vis[v]) dfs(v,3-color);
else if(vis[v]==color) return 0;
}
return 1;
}
来一个神奇的应用
传送门:https://www.luogu.org/problemnew/show/P1525
题目描述
S 城现有两座监狱,一共关押着N N名罪犯,编号分别为1-N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为c的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为 c的冲突事件。
每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到S 城Z 市长那里。公务繁忙的Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。
在详细考察了N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。
那么,应如何分配罪犯,才能使Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?
分析
二分大法好啊!
将我们要输出的答案(即冲突造成的破坏值)二分,设为mid,然后扫描边目录,将所有边权在mid之上的边都加入到图里(注意是无向边)。此时,如果该图为一张合法的二分图,那么意味着有连边的两个点不会在一个集合中,也就是说,任意的两个怨恨程度在mid之上的罪犯都可以不被放到同一个监狱里。
推代码
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int inf=1e9+7;
inline int read()
{
int ans=0,flag=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')flag=-1;c=getchar();}
while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();}
return flag*ans;}
struct Edge
{
int from,to,w;
}p[200019];
int n,m,L,R,cnt,head[200019];
void add_edge(int x,int y,int W)
{
cnt++;
p[cnt].from=head[x];
head[x]=cnt;
p[cnt].to=y;
p[cnt].w=W;
}
bool work(int mid)
{
queue <int> q;
int color[20009]={0};
for(int i=1;i<=n;i++)
if(!color[i])
{
q.push(i);
color[i]=1;
while(!q.empty())
{
int x=q.front();
q.pop();
for(int i=head[x];i;i=p[i].from)
if(p[i].w>=mid)
{
if(!color[p[i].to])//未染色
{
q.push(p[i].to);
if(color[x]==1)
color[p[i].to]=2;
else color[p[i].to]=1;
}
else if(color[p[i].to]==color[x])
return false;
}
}
}
return true;//是二分图
}
int main()
{
n=read(),m=read();
for(int i=1;i<=m;i++)//加边(无向图)
{
int x=read(),y=read(),w=read();
R=max(R,w);//二分右端点
add_edge(x,y,w);
add_edge(y,x,w);
}
R++;
while(R>L+1)
{
int mid=(L+R)>>1;
if(work(mid))
R=mid;
else L=mid;
}
printf("%d",L);
return 0;
}
拖到现在博主终于真的持续更新了啊崩溃ing
二.二分图的最大匹配
先说说通俗解释,我们可以把二分图中的黑白两个点集看做是男女一般来说这样至少可以保证集合内部不会那啥 ,如果一个黑点与一个白点之间有连边,大概就相当于一对男女之间互相有好感,而现在有个舞会,每个人只愿意与有好感的异性跳舞,现在我们就要凑出尽量多的舞伴,这就是二分图的最大匹配。
还是讲讲严格定义吧。在一个二分图中找到一些边来组成一个边集,使得边集内的边两两之间没有公共点,这就叫二分图的一个匹配,这也就对应上面的舞伴问题,毕竟一个人不能同时和多个人跳舞啊。如果这个边集内的边数不可能更大了,这就是二分图的最大匹配。如果存在一个匹配使得图中所有的点都被囊括了,这就称之为二分图的完美匹配。
tip:理解一下匹配与凑舞伴之间的条件的对应关系,这非常有利于二分图之后的学习与理解,二分图最经典的模型之一就是婚姻稳定问题。
我们先来看一道口暴 简单题,SHTSC2002
匈牙利模板题不多解释:
#include
using namespace std;
inline void write(int x){
if(x<0) x=-x,putchar('-');
if(x>9) write(x/10);
putchar('0'+x%10);return;
}
inline int read(){
char ch;
int flag=1;
while((ch=getchar())<'0'||ch>'9') if(ch=='-') flag=-1;
int ans=ch-48;
while((ch=getchar())>='0'&&ch<='9') ans=ans*10+ch-48;
return ans*flag;
}
int n,m;
int head[1001],vis[1001],match[1001];
int cnt=0,idx=0;
struct node{
int v,nt;
}e[5001];
inline void add(int u,int v){
cnt++;
e[cnt]=(node){v,head[u]};
head[u]=cnt;return;
}
bool dfs(int u){
for(int i=head[u];i;i=e[i].nt){
int v=e[i].v;
if(vis[v]==idx) continue;
vis[v]=idx;
if(!match[v]||dfs(match[v])){
match[v]=u;return 1;
}
}
return 0;
}
int main(){
n=read(),m=read();
int u,v;
int ans=0;
for(int i=1;i<=m;i++){
u=read(),v=read();
add(u+1,v+1);
}
for(int i=1;i<=n;i++){
idx++;
// memset(vis,0,sizeof())
ans+=dfs(i);
}
write(n-ans);
return 0;
}
那我们还是来一道难一点的题吧!
传送门
lxhgww最近迷上了一款游戏,在游戏里,他拥有很多的装备,每种装备都有2个属性,这些属性的值用[1,10000]之间的数表示。当他使用某种装备时,他只能使用该装备的某一个属性。并且每种装备最多只能使用一次。
游戏进行到最后,lxhgww遇到了终极boss,这个终极boss很奇怪,攻击他的装备所使用的属性值必须从1开始连续递增地攻击,才能对boss产生伤害。也就是说一开始的时候,lxhgww只能使用某个属性值为1的装备攻击boss,然后只能使用某个属性值为2的装备攻击boss,然后只能使用某个属性值为3的装备攻击boss……以此类推。
现在lxhgww想知道他最多能连续攻击boss多少次?
输入
输入的第一行是一个整数N,表示lxhgww拥有N种装备 接下来N行,是对这N种装备的描述,每行2个数字,表示第i种装备的2个属性值
输出
输出一行,包括1个数字,表示lxhgww最多能连续攻击的次数。
样例输入 [复制]
3
1 2
3 2
4 5
样例输出 [复制]
2
提示
【数据范围】 对于30%的数据,保证N<=1000 对于100%的数据,保证N<=1000000
标签
scoi2010省选
相当于是有n个二元组(xi,yi),每个二元组中只能选一个数,问从1开始最多选几个使得选出的数能连续不断,直接增广路推到首次不能再推的地方就行了。
代码:
#include
using namespace std;
#include
using namespace std;
inline int read(){
char ch;
int flag=1;
while((ch=getchar())<'0'||ch>'9') if(ch=='-') flag=-1;
int ans=ch-48;
while((ch=getchar())>='0'&&ch<='9') ans=ans*10+ch-48;
return ans*flag;
}
inline void write(int x){
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar('0'+x%10);
return;
}
int n;
int head[50005],vis[1000050];
int match[1000050];
struct node{
int v,nt;
}e[2000050];
int cnt=0,ans=0,idx=0;
inline void add(int u,int v){
cnt++;
e[cnt]=(node){v,head[u]};
head[u]=cnt;return;
}
int dfs(int u){
for(int i=head[u];i;i=e[i].nt){
int v=e[i].v;
if(vis[v]==idx) continue;
vis[v]=idx;
if(match[v]==-1||dfs(match[v])){
match[v]=u;
return 1;
}
}
return 0;
}
int main(){
n=read();
int x,y;
for(int i=1;i<=n;i++){
x=read(),y=read();
add(x,i);add(y,i);
}
memset(match,-1,sizeof(match));
for(int i=1;i<=10000;i++){
++idx;
if(!dfs(i)) break;
ans++;
}
write(ans);
return 0;
}