uoj79 一般图最大匹配 带花树学习(被虐

辣鸡蒟蒻原来的blog: http://www.elijahqi.win/2018/01/28/uoj79/

学习资料其一:http://www.csie.ntnu.edu.tw/~u91029/Matching.html

其二:2015 年中国国家队候选队员论文集,陈胤伯,《浅谈图的匹配算法及其应用》
从前一个和谐的班级,所有人都是搞OI的。有 n n

从前一个和谐的班级,所有人都是搞OI的。有
n
n
个是男生,有
0
0
个是女生。男生编号分别为
1,…,n
1,…,n

现在老师想把他们分成若干个两人小组写动态仙人掌,一个人负责搬砖另一个人负责吐槽。每个人至多属于一个小组。
有若干个这样的条件:第
v
v
个男生和第
u
u
个男生愿意组成小组。
请问这个班级里最多产生多少个小组?
输入格式
第一行两个正整数,
n,m
n,m
。保证
n≥2
n≥2

接下来
m
m
行,每行两个整数
v,u
v,u
表示第
v
v
个男生和第
u
u
个男生愿意组成小组。保证
1≤v,u≤n
1≤v,u≤n
,保证
v≠u
v≠u
,保证同一个条件不会出现两次。
输出格式
第一行一个整数,表示最多产生多少个小组。
接下来一行
n
n
个整数,描述一组最优方案。第
v
v
个整数表示
v
v
号男生所在小组的另一个男生的编号。如果
v
v
号男生没有小组请输出
0
0

样例一
input
10 20
9 2
7 6
10 8
3 9
1 10
7 1
10 9
8 6
8 2
8 1
3 1
7 5
4 7
5 9
7 8
10 4
9 1
4 8
6 3
2 5

output
5
9 5 6 10 2 3 8 7 1 4

样例二
input
5 4
1 5
4 2
2 1
4 3

output
2
2 1 4 3 0

限制与约定
1≤n≤500
1≤n≤500

1≤m≤124750
1≤m≤124750

时间限制:
1s
1s
空间限制:
256MB
256MB

256MB个点都是未匹配点的一条【交错路径】即 第一条边和最后一条边是未匹配边 特性:颠倒路径上的匹配与非匹配边可是使得匹配数+1并且改变匹配形式
【对称差集】两个集合 A 和 B 的「对称差集」定义为 A⊕B = (A∪B) - (A∩B)
补充:对于图上的两种匹配 计算对称差集 因为前面所说交错路径和交错环的定义 我不可能是奇环 于是我们可以使用对称差集原理来变换我们两种不同的匹配M⊕M* = P ,则M⊕P = M* 、 M*⊕P = M 计算对称差集缠上的六种情况都是交错路径或交错环这里写图片描述
【增广路定理】
从图上任意选取一个未匹配点 如果不能找到以此点作为端点的扩充路径则该点可以删去
补充定义:(边构成的集合称为匹配)匹配点:有另一个点的点非匹配点:孑然一身的点
证明从两大块入手 设当前匹配为M 图中最大匹配为M*
1、首先该点既不在M也不在M*上 那么随意删去
2、因为因为对于M说P是未匹配点所以P不在M上所以只有de两种情况可行
3、因为M找不到以P做端点的扩充路径 但是d蓝色的那个就是扩充路径 所以可以找到
所以d并不满足我可以删点的前提条件
4、对于M*来说因为显然可以通过颠倒匹配和最大匹配实现同样效果
由此出一个寻找最大匹配的方法:
1、一开始图上所有点都是未匹配点
2、将图上每个未匹配点都尝试作为扩充路径的端点
1)找到扩充路径将路径上匹配与未匹配颠倒 增加匹配值
2)找不到扩充路径就直接把这个点删去
所以找最大匹配的方法就是 不断选择一个端点寻找扩充路径 如果找不到了就停止此时图中的匹配就是最大匹配
【二分图中最大匹配的寻找】
随意选定一个未匹配点作为树根 建立搜索树 尝试列出所有的路径
因为是二分图中没有奇环 当两条路径重合的时候直接只做一条就okay
在二分图中 每条交错路径都在x&y之间来回 假如选定一个端点在x上开始作为交错树的树根那么另一个端点 未匹配点就会在Y上
这里写图片描述
一般图的交错树:
1、偶点到奇点:一定是未匹配的边
2、奇点到偶点:一定是匹配边
3、偶到偶:两个都是匹配点并且形成花
花上的点都可以成为偶点那么直接把花变成一个偶点即可
注:需要花上有一个奇点可以继续延伸那么花上所有点都将成为偶点
花的处理:
1、花托一定是他们的lca
2、将整个花分成两段分开处理
3、注意维护pre数组 一开始环上所有偶数点都是已经入队增广这次需要将奇点也入队增广 只要有一个奇数点增广成功我们就可以顺着pre将环上的路径全部修改正确
关于这个lca的处理
这里写图片描述
可以参照第一行第二张图来yy一下
代码中关于color的定义-1表示没有访问过 1 表示奇点 0表示偶点
关于lca 那里的一些特判 蒟蒻我到现在仍然想的不是很清楚 orz

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 550
#define M 134750
using namespace std;
inline char gc(){
    static char now[1<<16],*S,*T;
    if (T==S){T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=gc();}
    while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=gc();}
    return x*f;
}
struct node{
    int y,next;
}data[M<<1];
int h[N],match[N],n,m,q[N],l,r,tim,visit[N],fa[N],pre[N],col[N],num;
inline int find(int x){while(x!=fa[x]) x=fa[x]=fa[fa[x]];return x;}
inline int lca(int x,int y){
    ++tim;x=find(x);y=find(y);
    for (;;swap(x,y)){
        if(!x) continue;
        if (visit[x]==tim) return x;
        visit[x]=tim;
        if (match[x]) x=find(pre[match[x]]);else x=0;
    }
}
inline void change(int x,int y,int f){
    while(find(x)!=f){
        pre[x]=y;y=match[x];
        if (col[y]==1) col[y]=0,q[++r]=y;
        fa[x]=fa[y]=f;x=pre[y];
    }
}
inline bool check(int x){
    memset(col,-1,sizeof(col));memset(pre,0,sizeof(pre));col[x]=0;l=1;r=0;
    q[++r]=x;for (int i=1;i<=n;++i) fa[i]=i;
    while(l<=r){
        int x=q[l++];
        for (int i=h[x];i;i=data[i].next){
            int y=data[i].y;
            if (col[y]==-1){
                col[y]=1;pre[y]=x;
                if (!match[y]){
                    for (int last;x;y=last,x=pre[y])
                        last=match[x],match[x]=y,match[y]=x;return 1;
                }else q[++r]=match[y],col[match[y]]=0;
            }else if (!col[y]&&find(x)!=find(y)){
                int t=lca(x,y);change(x,y,t);change(y,x,t);
            }
        }
    }return 0;
}
int main(){
    freopen("uoj79.in","r",stdin);
    n=read();m=read();
    for (int i=1;i<=m;++i){
        int x=read(),y=read();
        data[++num].y=y;data[num].next=h[x];h[x]=num;
        data[++num].y=x;data[num].next=h[y];h[y]=num;
    }int ans=0;
    for (int i=1;i<=n;++i) if (!match[i]&&check(i)) ++ans;
    printf("%d\n",ans);for (int i=1;i<=n;++i) printf("%d ",match[i]);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值