ACM算法总结 一般图最大匹配



也就是在无向图中,两个结点之间有边表示这两个点可以匹配,然后要求出最大匹配。该算法为带花树,我现在并不理解它,只是当个板子记下来。

const int maxn=1005;
const int maxm=1e6+5;

struct daihuashu
{
    struct edge {int v,nxt;} e[maxm];
    int n,m,que[maxm],ql,qr,pre[maxn],tim=0,ans=0;
    int h[maxn],tot=0,match[maxn],f[maxn],tp[maxn],tic[maxn];

    daihuashu() {}
    daihuashu(int n,int m) {this->n=n; this->m=m;}

    void init(int n=0,int m=0)
    {
        tim=ans=ql=qr=tot=0;
        this->n=n; this->m=m;
        mem(match,0); mem(tic,0); mem(h,0);
    }

    int find(int x) {return f[x]==x?x:f[x]=find(f[x]);}
    void add(int u,int v) {e[++tot]=(edge){v,h[u]}; h[u]=tot;}
    void addb(int u,int v) {add(u,v); add(v,u);}
    int lca(int x,int y)
    {
        for(++tim;;swap(x,y)) if(x)
        {
            x=find(x);
            if(tic[x]==tim) return x;
            else tic[x]=tim,x=pre[match[x]];
        }
    }
    void shrink(int x,int y,int p)
    {
        while(find(x)!=p)
        {
            pre[x]=y; y=match[x];
            if(tp[y]==2) tp[y]=1,que[++qr]=y;
            if(find(x)==x) f[x]=p;
            if(find(y)==y) f[y]=p;
            x=pre[y];
        }
    }
    bool aug(int s)
    {
        REP(i,1,n) f[i]=i;
        mem(tp,0); mem(pre,0); tp[que[ql=qr=1]=s]=1;
        while(ql<=qr)
        {
            int x=que[ql++];
            for(int i=h[x],v=e[i].v;i;i=e[i].nxt,v=e[i].v)
            {
                if(find(v)==find(x) || tp[v]==2) continue;
                if(!tp[v])
                {
                    tp[v]=2; pre[v]=x;
                    if(!match[v])
                    {
                        for(int now=v,last,tmp;now;now=last)
                            last=match[tmp=pre[now]],match[now]=tmp,match[tmp]=now;
                        return true;
                    }
                    tp[match[v]]=1; que[++qr]=match[v];
                }
                else if(tp[v]==1)
                {
                    int l=lca(x,v);
                    shrink(x,v,l); shrink(v,x,l);
                }
            }
        }
        return false;
    }
    void run() {REP(i,1,n) ans+=(!match[i] && aug(i));}
}dhs;

其中 ans 表示最大匹配的数目,match 数组记录了每个结点与之匹配的结点编号(如果没有则为 0)。复杂度为 O ( n 3 ) O(n^3) O(n3)



  • 一道例题 Hard Problem :有一个无向图,现在为每个结点指定一个非负整数 d[i],问能否从无向图中构造一个子图,使得每个结点的度为 d[i] 。

做法:对于每个结点进行度拆点,比如一个结点的度为 3,那么就拆成 3 个结点;然后对于每一条边进行边拆点,比如有一条边 <1, 2>,那么拆成两个点;边拆点之后的两个点连边,边拆点的两个端点分别与对应的度拆点的所有点连边;然后求一般图最大匹配,如果每个(拆点后的)结点都可以匹配,那么就存在方案。

这是因为,如果每个结点都可以匹配,如果匹配的两个结点都是边拆点,那么表示舍弃这条边,如果匹配的两个结点一个是度拆点一个是边拆点,那么表示满足了一个度。或者可以这么考虑,每一条边拆点之后,要么两个点相互匹配(表示舍弃这条边),要么这两个点分别和另外两个点匹配(表示满足两个度)。

代码:

#define DIN freopen("input.txt","r",stdin);
#define DOUT freopen("output.txt","w",stdout);
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
#define pb push_back
using namespace std;
typedef long long LL;
typedef vector<int> VI;
typedef pair<int,int> P;
int read()
{
    int x=0,flag=1; char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=1005;
const int maxm=1e6+5;

struct daihuashu
{
    struct edge {int v,nxt;} e[maxm];
    int n,m,que[maxm],ql,qr,pre[maxn],tim=0,ans=0;
    int h[maxn],tot=0,match[maxn],f[maxn],tp[maxn],tic[maxn];

    daihuashu() {}
    daihuashu(int n,int m) {this->n=n; this->m=m;}

    void init(int n=0,int m=0)
    {
        tim=ans=ql=qr=tot=0;
        this->n=n; this->m=m;
        mem(match,0); mem(tic,0); mem(h,0);
    }

    int find(int x) {return f[x]==x?x:f[x]=find(f[x]);}
    void add(int u,int v) {e[++tot]=(edge){v,h[u]}; h[u]=tot;}
    void addb(int u,int v) {add(u,v); add(v,u);}
    int lca(int x,int y)
    {
        for(++tim;;swap(x,y)) if(x)
        {
            x=find(x);
            if(tic[x]==tim) return x;
            else tic[x]=tim,x=pre[match[x]];
        }
    }
    void shrink(int x,int y,int p)
    {
        while(find(x)!=p)
        {
            pre[x]=y; y=match[x];
            if(tp[y]==2) tp[y]=1,que[++qr]=y;
            if(find(x)==x) f[x]=p;
            if(find(y)==y) f[y]=p;
            x=pre[y];
        }
    }
    bool aug(int s)
    {
        REP(i,1,n) f[i]=i;
        mem(tp,0); mem(pre,0); tp[que[ql=qr=1]=s]=1;
        while(ql<=qr)
        {
            int x=que[ql++];
            for(int i=h[x],v=e[i].v;i;i=e[i].nxt,v=e[i].v)
            {
                if(find(v)==find(x) || tp[v]==2) continue;
                if(!tp[v])
                {
                    tp[v]=2; pre[v]=x;
                    if(!match[v])
                    {
                        for(int now=v,last,tmp;now;now=last)
                            last=match[tmp=pre[now]],match[now]=tmp,match[tmp]=now;
                        return true;
                    }
                    tp[match[v]]=1; que[++qr]=match[v];
                }
                else if(tp[v]==1)
                {
                    int l=lca(x,v);
                    shrink(x,v,l); shrink(v,x,l);
                }
            }
        }
        return false;
    }
    void run() {REP(i,1,n) ans+=(!match[i] && aug(i));}

    int from[maxn],to[maxn],du[maxn];
    void solve()
    {
        n=read(),m=read();
        int cnt=0;
        VI di[maxn];
        REP(i,1,m)
        {
            int x=read(),y=read();
            from[i]=x; to[i]=y;
        }
        REP(i,1,n) du[i]=read();
        REP(i,1,n) REP(j,1,du[i]) di[i].pb(++cnt);
        REP(i,1,m)
        {
            int x=from[i],y=to[i];
            for(int j:di[x]) addb(j,cnt+1);
            for(int j:di[y]) addb(j,cnt+2);
            addb(cnt+1,cnt+2);
            cnt+=2;
        }
        n=cnt;
        run();
        int flag=1;
        REP(i,1,n) if(!match[i]) flag=0;
        puts(flag?"YES":"NO");
    }
}dhs;

int main()
{
    int T=read();
    REP(i,1,T)
    {
        printf("Case %d: ",i);
        dhs.init();
        dhs.solve();
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值