例题#
分析:##
裸题,牛栏作为一个点集,牛作为另一个,牛喜欢牛栏则从牛向牛栏连一条边跑匈牙利就得了,邻接表开大点
代码:##
#include<bits/stdc++.h>
#define MAXN (2000+5)
using namespace std;
inline int read(){
int cnt=0,f=1;char c;
c=getchar();
while(!isdigit(c)){
if(c=='-')f=-f;
c=getchar();
}
while(isdigit(c)){
cnt=cnt*10+c-'0';
c=getchar();
}
return cnt*f;
}
int n,m,nxt[2*MAXN],first[2*MAXN],to[2*MAXN],match[2*MAXN],x,y,ans,tot,res;
bool vis[2*MAXN];
void add(int x,int y){
nxt[++tot]=first[x];
first[x]=tot;
to[tot]=y;
}
int find(int u){
for(register int i=first[u];i;i=nxt[i]){
int v=to[i];
if(vis[v])continue;
else {
vis[v]=true;
if(match[v]==-1||find(match[v])) {
match[v]=u;
match[u]=v;
return 1;
}
}
}
return 0;
}
int hungary(){
for(register int i=1;i<=n;i++){
for(register int j=1;j<=2*n+1;j++)vis[j]=false;
if(match[i]==-1)ans+=find(i);
}
return ans;
}
int main(){
n=read();m=read();
for(register int i=1;i<=n+m;i++)match[i]=-1;
for(register int i=1;i<=n;i++){
x=read();
for(register int j=1;j<=x;j++){
y=read();add(i,y+n);
}
}
printf("%d",hungary());
return 0;
}
题目链接:##
分析:##
行和列分别作为两边的点,将原图的点作为边建图
因为行和列的交换并不会改变图的形态而只是交换编号,所以对最终答案没有影响
所以读入图的时候如果Map[i,j]1就从i往j连一条边
然后跑最大匹配就可以了,跑完若ansn则说明所有行和列都有对应匹配,那么说明有方案,如果ans<n说明不是所有的行和列都能有对应匹配,所以就不能通过变换达成对角线(每行每列都有匹配且不和其他行/列的匹配冲突),说明没有方案
多组数据,记得初始化
代码:##
#include<bits/stdc++.h>
#define N (20000+5)
#define M (200000+5)
using namespace std;
inline int read(){
int cnt=0,f=1;char c;
c=getchar();
while(!isdigit(c)){
if(c=='-')f=-f;
c=getchar();
}
while(isdigit(c)){
cnt=cnt*10+c-'0';
c=getchar();
}
return cnt*f;
}
int n,t,nxt[M],first[N],to[M],x,match[N],tot,res,ans;
bool vis[N];
void add(int x,int y){
nxt[++tot]=first[x];
first[x]=tot;
to[tot]=y;
}
int find(int u){
for(register int i=first[u];i;i=nxt[i]){
int v=to[i];
if(vis[v]) continue;
else {
vis[v]=true;
if(match[v]==-1||find(match[v])){
match[v]=u;
return 1;
}
}
}
return 0;
}
int hungary(){
for(register int i=1;i<=n;i++) {
for(register int j=1;j<=n;j++)vis[j]=false;
ans+=find(i);
}
return ans;
}
int main(){
t=read();
while(t--) {
n=read();
for(register int i=1;i<=n;i++)match[i]=-1;
for(register int i=1;i<=n;i++)
for(register int j=1;j<=n;j++) {
x=read();if(x)add(i,j);
}
res=hungary();
if(res==n)printf("Yes
");
else printf("No
");
for(register int i=1;i<=n;i++) first[i]=0;
for(register int i=1;i<=tot;i++) nxt[i]=to[i]=0;
tot=0;res=0;ans=0;
}
return 0;
}
另外上面代码的初始化非常猥琐……memset太慢了,所以自己根据n的大小来初始化的不过并没有快多少
题目链接:##
分析:##
由于每张纸片是(1*2)的,并且每张纸片覆盖的两个格子的坐标((i,j))必有((i+j))奇偶性不相同,所以考虑以((i+j))的奇偶性建立两个点集,然后只要没洞/没越界都可以把相邻两个格子代表的点连上一条边,跑出最大匹配(M),如果(M*2)=(n*m-k)则说明有方案,反之则无。
代码:##
#include<bits/stdc++.h>
#define N 500
#define M 10000
using namespace std;
inline int read(){
int cnt=0,f=1;char c;
c=getchar();
while(!isdigit(c)){
if(c=='-')f=-f;
c=getchar();
}
while(isdigit(c)){
cnt=cnt*10+c-'0';
c=getchar();
}
return cnt*f;
}
int n,m,t,Map[N][N],color[N][N],white=0,black=0,x,y,nxt[M],first[N],to[M],ans,tot,match[N];
bool vis[N];
void add(int x,int y){
nxt[++tot]=first[x];
first[x]=tot;
to[tot]=y;
}
void build_map(){
/*------------------染色,编号------------------*/
for(register int i=1;i<=n;i++)
for(register int j=1;j<=m;j++){
if((i+j)%2)color[i][j]=++white;
else color[i][j]=++black;
}
/*------------------染色,编号------------------*/
/*--------------------连边----------------------*/
for(register int i=1;i<=n;i++)
for(register int j=1;j<=m;j++)
if(!Map[i][j]&&(i+j)%2){
if(i-1>0&&!Map[i-1][j])add(color[i][j],color[i-1][j]); //上
if(j-1>0&&!Map[i][j-1])add(color[i][j],color[i][j-1]); //左
if(i+1<=n&&!Map[i+1][j])add(color[i][j],color[i+1][j]); //下
if(j+1<=m&&!Map[i][j+1])add(color[i][j],color[i][j+1]); //右
}
/*--------------------连边----------------------*/
}
int find(int u){
for(register int i=first[u];i;i=nxt[i]){
int v=to[i];
if(vis[v])continue;
else {
vis[v]=1;
if(match[v]==-1||find(match[v])){
match[v]=u;
return 1;
}
}
}
return 0;
}
int hungary(){
for(register int i=1;i<=white;i++){
for(register int j=1;j<=black;j++)vis[j]=0;
ans+=find(i);
}
return ans;
}
int main(){
n=read();m=read();t=read();
for(register int i=1;i<=t;i++) { x=read();y=read();Map[y][x]=1; } /*这里巨坑……输入的是坐标形式的(x,y)所以要反过来打标记*/
build_map();
for(register int i=1;i<=black;i++)match[i]=-1;
int res=hungary();
// cout<<res;return 0;
if(res*2==n*m-t)printf("YES");
else printf("NO");
return 0;
}
拓展#
1 最小点覆盖##
最小点覆盖要求用最少的点(任意集合均可)让每条边都至少与一个点相关联。
说人话就是在二分图里面选一些点出来,让每个边都有至少一个端点。
可以证明最小点覆盖=最大匹配(M)
简要证明如下:
- (M)个是充分的:将这些点覆盖(M)条匹配边,则其他边也一定被覆盖。若有其他边未被覆盖,则将此边加入可得到一个更大的匹配(M'),与(M)为最大匹配矛盾。
- (M)个是必要的:仅考虑这(M)条匹配边,根据最大匹配的定义可以知道,由于这(M)条匹配边没有公共点,所以至少需要(M)个顶点将其全部覆盖。
例题:###
题意:###
一个平面内给出一些障碍物,每次操作可以消除整行或整列的障碍物,问最少需要操作多少次?
分析:###
这道题可以将行和列转化成二分图里的两个点集,而障碍物作为边的形式存在,若障碍物在(i)行(j)列,则从(i)向(j)连一条边。一次消除一行或一列的操作可以看做是选一个点,而这个点所覆盖的边(即障碍物)将被“清除”,于是问题转化为求二分图的最小点覆盖,最小点覆盖=最大匹配数,跑匈牙利即可。
事实上,有不少问题也采用了类似的转化方式(即若题目所给的一条信息中有两个特征,那么将这两个特征分别作为(X)点集和(Y)点集中的点,而其信息本身作为边),但做题时切不能生搬硬套。
代码:###
#include<bits/stdc++.h>
#define N (40000+5)
#define M (200000+5)
using namespace std;
inline int read(){
int cnt=0,f=1;char c;
c=getchar();
while(!isdigit(c)){
if(c=='-')f=-f;
c=getchar();
}
while(isdigit(c)){
cnt=cnt*10+c-'0';
c=getchar();
}
return cnt*f;
}
int n,m,x,y;
int nxt[M],first[N],to[M],match[N],ans,tot;
bool vis[N];
void add(int x,int y){
nxt[++tot]=first[x];
first[x]=tot;
to[tot]=y;
}
int find(int u){
for(register int i=first[u];i;i=nxt[i]) {
int v=to[i];
if(vis[v]) continue;
else {
vis[v]=true;
if(match[v]==-1||find(match[v])) {
match[v]=u;
return 1;
}
}
}
return 0;
}
int hungary() {
for(register int i=1;i<=m;i++) match[i]=-1;
for(register int i=1;i<=n;i++){
for(register int j=1;j<=m;j++) vis[j]=false;
ans+=find(i);
}
return ans;
}
int main(){
n=read();m=read();
for(register int i=1;i<=m;i++) {
x=read();y=read();add(x,y);
}
printf("%d",hungary());
return 0;
}
话说这个题为什么初始化(vis)数组的时候把(j)打成(i)了还有64啊……
2 最大独立集##
最大独立集:在包含N个点的图G中选出m个点,使得这些点之间两两没有连边,当m取得最大值时称这些选出的点集为图G的最大独立集
对于二分图中的最大独立集有一个重要结论:
二分图中的最大独立集=节点数-最小覆盖点数
简要证明一下:
在一个二分图G中,每一条边中都至少有一个顶点在G的覆盖集中,所以对于每条边上的未选点,其对面的点都在覆盖集中,所以剩下的点两两不会有连边,这就是一个独立集。此时这个独立集与覆盖集互补。于是有最大独立集与最小覆盖点集互补。
有了以上结论,结合“最小覆盖点=最大匹配”可以得到最大独立集=节点数-最大匹配
例题:###
WOJ上的,学校电脑不方便粘网址……
分析:###
感觉这个题很毒啊,数据真的保证了男女配对不冲突吗
要是来一组
3 3
1 2
2 3
3 1
那不就GG了吗……1和3明显gay住了啊
然后我就开始手动编号,先无脑连边建个乱七八糟不知道是什么的图再递归染色,写了一个多小时尝试各种姿势建图,然后在WOJ上WA成了傻逼……后来去问了下梁老关于这个题数据的问题,梁老表示这个题数据好像是有点锅,唔那就不管了吧,无脑连边水个AC
没错我在AC面前屈服了
代码:###
并没有大锅但是GG了的代码长这样:####
#include<bits/stdc++.h>
#define N 10000
#define M 20000
using namespace std;
inline int read(){
int cnt=0,f=1;char c;
c=getchar();
while(!isdigit(c)){
if(c=='-')f=-f;
c=getchar();
}
while(isdigit(c)){
cnt=cnt*10+c-'0';
c=getchar();
}
return cnt*f;
}
int n,m,x,y,nxt[M],first[N],to[M],tot,match[N],ans=0,a[N],b[N];
int belongs[N];
bool vis[N],built[N];
void add(int x,int y){
nxt[++tot]=first[x];
first[x]=tot;
to[tot]=y;
}
void build_map(int u){
built[u]=1;
if(belongs[u]==-1)belongs[u]=1;
for(register int i=first[u];i;i=nxt[i]){
int v=to[i];
belongs[v]=1-belongs[u];
// cout<<"v="<<v<<" belongsto"<<belongs[v]<<endl;
build_map(v);
}
return;
}
int find(int u){
for(register int i=first[u];i;i=nxt[i]){
int v=to[i];
if(vis[v]) continue;
else {
vis[v]=true;
if(match[v]==-1||find(match[v])){
match[v]=u;
return 1;
}
}
}
return 0;
}
int hungary(){
for(register int i=1;i<=n;i++)match[i]=-1;
for(register int i=1;i<=n;i++){
for(register int j=1;j<=n;j++) vis[j]=false;
if(belongs[i]==1)ans+=find(i);
}
return ans;
}
int main(){
n=read();m=read();
for(register int i=1;i<=n;i++) belongs[i]=-1,built[i]=0;
for(register int i=1;i<=m;i++){
a[i]=read();b[i]=read();
a[i]++;b[i]++;add(a[i],b[i]);
}
// belongs[a[1]]=1;
// for(register int i=1;i<=m;i++)cout<<"a[i]="<<a[i]<<" b[i]="<<b[i]<<endl;
for(register int i=1;i<=m;i++)
if(!built[a[i]]) build_map(a[i]);
for(register int i=1;i<=tot;i++)nxt[i]=to[i]=0;
for(register int i=1;i<=n;i++)first[i]=0;
tot=0;
for(register int i=1;i<=n;i++){
if(belongs[a[i]]==1)add(a[i],b[i]);
if(belongs[b[i]]==1)add(b[i],a[i]);
}
// for(register int i=1;i<=n;i++)cout<<belongs[i];
printf("%d",n-hungary());
return 0;
}
AC代码,不要把这玩意当成上面例题的题解,当个求最大独立集板子用……####
#include<bits/stdc++.h>
#define N 10000
#define M 20000
using namespace std;
inline int read(){
int cnt=0,f=1;char c;
c=getchar();
while(!isdigit(c)){
if(c=='-')f=-f;
c=getchar();
}
while(isdigit(c)){
cnt=cnt*10+c-'0';
c=getchar();
}
return cnt*f;
}
int n,m,x,y,nxt[M],first[N],to[M],tot,match[N],ans=0;
bool vis[N],built[N];
void add(int x,int y){
nxt[++tot]=first[x];
first[x]=tot;
to[tot]=y;
}
int find(int u){
for(register int i=first[u];i;i=nxt[i]){
int v=to[i];
if(vis[v]) continue;
else {
vis[v]=true;
if(match[v]==-1||find(match[v])){
match[v]=u;
return 1;
}
}
}
return 0;
}
int hungary(){
for(register int i=1;i<=n;i++)match[i]=-1;
for(register int i=1;i<=n;i++){
for(register int j=1;j<=n;j++) vis[j]=false;
ans+=find(i);
}
return ans;
}
int main(){
n=read();m=read();
for(register int i=1;i<=m;i++){
x=read();y=read();
x++;y++;add(x,y);
}
// for(register int i=1;i<=n;i++)cout<<belongs[i];
printf("%d",n-hungary());
return 0;
}
3 最小边覆盖##
边覆盖集:在图G中选出一些边,构成的能使得G中所有顶点都在某条边上的边集。
最小边覆盖:所有的边覆盖集中最小的一个集合。
最小边覆盖=最大独立集=n-最大匹配