uoj 79 一般图最大匹配

36 篇文章 4 订阅


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

现在老师想把他们分成若干个两人小组写动态仙人掌,一个人负责搬砖另一个人负责吐槽。每个人至多属于一个小组。

有若干个这样的条件:第  v v 个男生和第  u u 个男生愿意组成小组。

请问这个班级里最多产生多少个小组?

输入格式

第一行两个正整数, n,m n,m。保证  n2 n≥2

接下来  m m 行,每行两个整数  v,u v,u 表示第  v v 个男生和第  u u 个男生愿意组成小组。保证  1v,un 1≤v,u≤n,保证  vu v≠u,保证同一个条件不会出现两次。

输出格式

第一行一个整数,表示最多产生多少个小组。

接下来一行  n n 个整数,描述一组最优方案。第  v v 个整数表示  v v 号男生所在小组的另一个男生的编号。如果  v v 号男生没有小组请输出  0


模板:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 1e3 + 5;
const int maxm = 1e6 + 5;
int n, m, edgecnt, head[maxm], f[maxn];
int match[maxn];
int id[maxn];// 这个点是什么点 -1没访问过, 0:s点,1;t点
int que[maxn];//要扩展的队列————也就是我们要尝试帮谁换配偶
int pre[maxn];//在这次过程中,x的新配偶是谁
int tim, book[maxn]; //对于lca的标记以及时间轴
struct node
{
    int u, v, next;
}edge[maxm];
void addEdge(int u, int v)
{
    edge[edgecnt].v = v;
    edge[edgecnt].next = head[u];
    head[u] = edgecnt++;
}
int Find(int x)
{
    return f[x] == x ? x : f[x] = Find(f[x]);
}
int lca(int x, int y)
{
    tim++;
    while(book[x] != tim)
    {
        if(x != 0)
        {
            x = Find(x);
            if(book[x] == tim) return x;
            book[x] = tim;
            if(match[x] != 0) x = Find(pre[match[x]]);
             //因为在之前我们知道,每一个S点的配偶(也就是T点)的pre 都是指向他的父亲的,于是就直接这么跳就可以了
            //还有要注意的是,一定要先去到花根,因为他们现在已经是一个点了,只有花根的pre才指向他们真正的父亲
            else x = 0;
        }
        swap(x, y);
    }
    return x;
}
int st, ed;
void change(int x, int y, int k)//环  出现的是x---y的连边  已知根是k
{
    while(Find(x) != k)
    {
        pre[x] = y;
        int z = match[x];
        id[z] = 0;
        que[ed++] = z;
//        if(ed >= maxn-1) ed = 1;
        if(Find(z) == z) f[z] = k;
        if(Find(x) == x) f[x] = k;
        y = z;
        x = pre[y];

    }
}
void check(int x)//尽量帮助x寻找增广路
{
    for(int i = 1; i <= n; i++) f[i] = i, id[i] = -1;
    st = 1;
    ed = 2;
    que[st] = x;
    id[x] = 0;
    while(st != ed)
    {
        int u = que[st];
        for(int i = head[u]; i != -1; i = edge[i].next)
        {
            int to = edge[i].v;
            if(match[to] == 0 && to != x)
                //当然match[X]=0,但X(这次来寻找配偶的点)并不是一个可行的东西,所以不能算可行解
            {
                pre[to] = u;
                int last, t, now = to;
                while(now != 0)
                {
                    t = pre[now];
                    last = match[t];
                    match[t] = now;
                    match[now] = t;
                    now = last;
                }
                return;
            }
            if(id[to] == -1)//找到一个没有访问过的点————进行扩展  
            {
                id[to] = 1;
                pre[to] = u;//先假设他与x相连  
                id[match[to]] = 0;
                que[ed++] = match[to];
//                if(ed >= maxn-1) ed = 1;
            }
            else if(id[to] == 0 && Find(u) != Find(to))//出现一个以前未处理过的奇环  
            {
                int g = lca(u, to);
                change(u, to, g);
                change(to, u, g);
            }
        }
        st++;
//        if(st >= maxn-1) st = 1;
    }
}
int main()
{
    memset(book, 0, sizeof(book));
    memset(match, 0, sizeof(match));
    memset(head, -1, sizeof(head));
    edgecnt = 0;
    tim = 0;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; i++)
    {
        int x, y;
        scanf("%d%d", &x, &y);
        addEdge(x, y);
        addEdge(y, x);
    }
    for(int i = 1; i <= n; i++)
        if(match[i] == 0)
            check(i);
    int ans = 0;
    for(int i = 1; i <= n; i++)
        if(match[i] != 0) ans++;
    printf("%d\n", ans/2);
    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、付费专栏及课程。

余额充值