[Kuangbin带你飞]专题十 匹配问题

A->L:二分图最大匹配

二分图最大匹配最常用的是匈牙利算法,用DFS找增广路,时间复杂度O(n*m),在稠密图中简单又好用。当数据范围较大时,可以选用Hopcroft_Carp算法,BFS多次增广,优化之后的时间复杂度为O(sqrt(n)*m)。

几条重要的性质:

最大匹配=最小顶点覆盖

DAG最小路径覆盖=顶点数-最大匹配数

最大独立集=顶点数-最大匹配数

A. Fire Net -HDU1045: 每选择一个点,和这个点同行或同列没有被墙挡住的点就都不能选择。

难点在于建图。根据这个图的性质,每一行中没有被墙分开的所有点合并,算一行。例如....#....在建图时算两行,....#...#..算三行。对列也进行相同的处理。然后求最大匹配即可。

D. 棋盘游戏 -HDU1281: 基于A题的规则,存在某些“关键点”,只要不选择这些点,就无法选择最多的点。求关键点的数目。

n*m<=1e4,枚举每个可以选择的点,将其取消后再计算最大匹配,如果与原图的最大匹配不相等则该点是一个关键点。

E. Swap -HDU2819: 给出一个0-1矩阵,要求计算一个方案,交换矩阵的某些行或某些列,使得矩阵左对角线全为1。

通过类比矩阵的秩,可以得到:如果只进行行变换时无解,那么只进行列变换或行列都变换也无解。计算原图的最小点覆盖即最大匹配,如果不等于n则无解。在记录匹配路径时,从前往后扫,如果linker[i]!=i则是一条路径,同时用linker[i]的信息进行交换使得linker[i]==i。

F. Rain on Your Parade -HDU2389: 这个用匈牙利会超时。。可以用Hopcroft_Carp算法。

H. Antenna Placement -POJ3020: 题意略坑,基站只能放在城市,但信号覆盖范围可以包括空地,在建图时也不需要向空地连边。只考虑所有的城市求最大独立集即可。

K. Treasure Exploration -POJ2594: 给定DAG,求最小路径覆盖。

坑点是如果DAG中的有向边有相交情况,那么就不能直接求最大匹配。需要先用Floyd求一个传递闭包,可以认为是类似路径压缩的操作。然后再求最大匹配。

L. Cat VS Dog -HDU3829: 有一群猫、狗、人。每个人讨厌一只动物并喜欢一只动物。现需要选择一些动物,如果某人喜欢的被选中而不喜欢的没选中,他就会高兴。求最多可使多少人高兴。

个人认为建图比较骚。方法是如果有两人喜欢的动物恰好是对方不喜欢的,就在这两个人之间连边。答案即为新图的最小顶点覆盖。

Hopcroft_Carp算法模板(以F-HDU2389为例):

#include<bits/stdc++.h>
#define maxn 3050
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;

int T,t,n,m;
vector<int>maze[maxn];
int linkx[maxn],linky[maxn],vis[maxn];
int v[maxn],dis,dx[maxn],dy[maxn];
struct node
{
    int x,y;
}e[maxn],w[maxn];

bool bfs()
{
    dis=INF;
    memset(dx,-1,sizeof(dx));
    memset(dy,-1,sizeof(dy));
    queue<int>q;
    for(int i=1;i<=m;i++)
    {
        if(linkx[i]==-1)
        {
            q.push(i);
            dx[i]=0;
        }
    }
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        if(dx[u]>dis)break;
        int len=maze[u].size();
        for(int i=0;i<len;i++)
        {
            int v=maze[u][i];
            if(dy[v]==-1)
            {
                dy[v]=dx[u]+1;
                if(linky[v]==-1)dis=dy[v];
                else
                {
                    dx[linky[v]]=dy[v]+1;
                    q.push(linky[v]);
                }
            }
        }
    }
    return (dis!=INF);
}

bool dfs(int u)
{
    int len=maze[u].size();
    for(int i=0;i<len;i++)
    {
        int v=maze[u][i];
        if(!vis[v]&&dy[v]==dx[u]+1)
        {
            vis[v]=1;
            if(linky[v]!=-1&&dy[v]==dis)continue;
            if(linky[v]==-1||dfs(linky[v]))
            {
                linky[v]=u;
                linkx[u]=v;
                return true;
            }
        }
    }
    return false;
}

int hopcroft_karp()
{
    int res=0;
    memset(linkx,-1,sizeof(linkx));
    memset(linky,-1,sizeof(linky));
    while(bfs())
    {
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=m;i++)
        {
            if(linkx[i]==-1&&dfs(i))
                res++;
        }
    }
    return res;
}

int main()
{
    scanf("%d",&T);
    int kase=0;
    while(T--)
    {
        scanf("%d%d",&t,&m);
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&e[i].x,&e[i].y,&v[i]);
        }
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&w[i].x,&w[i].y);
        }
        for(int i=1;i<=m;i++)
            maze[i].clear();
        for(int i=1;i<=m;i++)
        {
            for(int j=1;j<=n;j++)
            {
                int len=abs(e[i].x-w[j].x)+abs(e[i].y-w[j].y);
                if(len<=t*v[i])
                    maze[i].push_back(j);
            }
        }
        int ans=hopcroft_karp();
        printf("Scenario #%d:\n%d\n\n",++kase,ans);
    }
    return 0;
}

M->O:二分图多重匹配

多重匹配即某些点可以和多条匹配边相连。方法是:设立一个超级源点S和超级汇点T,从S向原图X部每个点、从原图Y部每个点向T都连一条容量为相应顶点容量的边,原图各边保留容量为1。原图的多重最大匹配即为新图从S到T的最大流。

N. Optimal Milking -POJ2112: 在二分图多重匹配的模型中,求达到最大匹配时最长匹配边的最小值。

最大值最小,显然需要二分地求解。用Floyd预处理出各点之间的最短距离,每次用mid判断时都去掉原图中长度大于mid的边,看最大匹配是否发生变化。

二分图多重匹配模板(以N-POJ2112为例):

#include<bits/stdc++.h>
#define maxn 255
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;

int n,m,k,no;
int dis[maxn][maxn];
int level[maxn],iter[maxn];
struct node
{
    int to;
    int cap,rev;
    node(int a,int b,int c){to=a;cap=b;rev=c;}
};
vector<node>maze[maxn];

void add(int a,int b,int x)
{
    maze[a].push_back(node(b,x,maze[b].size()));
    maze[b].push_back(node(a,0,maze[a].size()-1));
}

void floyd()
{
    for(int k=1;k<=n+m;k++)
    {
        for(int i=1;i<=n+m;i++)
        {
            for(int j=1;j<=n+m;j++)
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
        }
    }
}

void bfs(int s)
{
    queue<int>q;
    memset(level,-1,sizeof(level));
    level[s]=0;
    q.push(s);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int i=0;i<maze[u].size();i++)
        {
            node &tmp=maze[u][i];
            int v=tmp.to;
            if(tmp.cap>0&&level[v]<0)
            {
                level[v]=level[u]+1;
                q.push(v);
            }
        }
    }
}

int dfs(int s,int t,int f)
{
    if(s==t)return f;
    for(int &i=iter[s];i<maze[s].size();i++)
    {
        node &tmp=maze[s][i];
        int v=tmp.to;
        if(tmp.cap>0&&level[s]<level[v])
        {
            int cnt=dfs(v,t,min(f,tmp.cap));
            if(cnt>0)
            {
                tmp.cap-=cnt;
                maze[v][tmp.rev].cap+=cnt;
                return cnt;
            }
        }
    }
    return 0;
}

int dinic(int s,int t)
{
    int ans=0;
    while(1)
    {
        bfs(s);
        if(level[t]<0)break;
        memset(iter,0,sizeof(iter));
        int tmp;
        while((tmp=dfs(s,t,INF))>0)
            ans+=tmp;
    }
    return ans;
}

bool judge(int x)
{
    int s=0,t=n+m+1;
    for(int i=0;i<=t;i++)
        maze[i].clear();
    for(int i=1;i<=n;i++)
        add(s,i,k);
    for(int i=n+1;i<=n+m;i++)
        add(i,t,1);
    for(int i=1;i<=n;i++)
    {
        for(int j=n+1;j<=n+m;j++)
        {
            if(dis[i][j]<=x)
                add(i,j,1);
        }
    }
    int ans=dinic(s,t);
    if(ans==m)return true;
    return false;
}

int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n+m;i++)
    {
        for(int j=1;j<=n+m;j++)
        {
            scanf("%d",&dis[i][j]);
            if(dis[i][j]==0)
                dis[i][j]=INF;
            if(i==j)dis[i][j]=0;
        }
    }
    floyd();
    int l=0,r=INF;
    while(r>l+1)
    {
        int mid=(l+r)>>1;
        if(judge(mid))r=mid;
        else l=mid;
    }
    printf("%d\n",r);
    return 0;
}

P. 奔小康赚大钱 -HDU2255: 二分图最大权匹配

最大权匹配使用KM算法,时间复杂度O(nx^2*ny)。

#include<bits/stdc++.h>
#define maxn 305
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;

int n,nx,ny;
int maze[maxn][maxn];
int linker[maxn],lx[maxn],ly[maxn];
int slack[maxn],visx[maxn],visy[maxn];

bool dfs(int u)
{
    visx[u]=1;
    for(int v=1;v<=ny;v++)
    {
        if(visy[v])continue;
        int tmp=lx[u]+ly[v]-maze[u][v];
        if(tmp==0)
        {
            visy[v]=1;
            if(linker[v]==-1||dfs(linker[v]))
            {
                linker[v]=u;
                return true;
            }
        }
        else if(slack[v]>tmp)
            slack[v]=tmp;
    }
    return false;
}

int km()
{
    memset(linker,-1,sizeof(linker));
    memset(ly,0,sizeof(ly));
    for(int i=1;i<=nx;i++)
    {
        lx[i]=-INF;
        for(int j=1;j<=ny;j++)
            lx[i]=max(lx[i],maze[i][j]);
    }
    for(int u=1;u<=nx;u++)
    {
        for(int i=1;i<=ny;i++)
            slack[i]=INF;
        while(1)
        {
            memset(visx,0,sizeof(visx));
            memset(visy,0,sizeof(visy));
            if(dfs(u))break;
            int tmp=INF;
            for(int i=1;i<=ny;i++)
            {
                if(!visy[i]&&tmp>slack[i])
                    tmp=slack[i];
            }
            for(int i=1;i<=nx;i++)
            {
                if(visx[i])
                    lx[i]-=tmp;
            }
            for(int i=1;i<=ny;i++)
            {
                if(visy[i])ly[i]+=tmp;
                else slack[i]-=tmp;
            }
        }
    }
    int res=0;
    for(int i=1;i<=ny;i++)
    {
        if(linker[i]!=-1)
            res+=maze[linker[i]][i];
    }
    return res;
}

int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
                scanf("%d",&maze[i][j]);
        }
        nx=ny=n;
        printf("%d\n",km());
    }
    return 0;
}

Q. Tour -HDU3488: 有向环最小权覆盖

方法是:把每个点拆成左点i和右点i+n。超级源点和左点连边,超级汇点和右点连边,原图边保留从相应左点到相应右点的边从S到T跑最小费用最大流,答案即为新图的最小费用。

#include<bits/stdc++.h>
#define maxn 405
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;

int t,n,m,no,a,b,x;
int head[maxn],pre[maxn],dis[maxn];
int vis[maxn],maze[maxn][maxn];
struct node
{
    int to,nxt;
    int cap,flow,cost;
}e[maxn*maxn];

void add(int from,int to,int cap,int cost)
{
    e[no].to=to;
    e[no].nxt=head[from];
    e[no].flow=0;
    e[no].cap=cap;
    e[no].cost=cost;
    head[from]=no++;
}

void init()
{
    memset(head,-1,sizeof(head));
    no=0;
    for(int i=0;i<maxn;i++)
    {
        for(int j=0;j<maxn;j++)
            maze[i][j]=INF;
    }
}

bool spfa(int s,int t)
{
    queue<int>q;
    memset(vis,0,sizeof(vis));
    memset(pre,-1,sizeof(pre));
    for(int i=0;i<=t;i++)dis[i]=INF;
    q.push(s);
    dis[s]=0,vis[s]=1;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        vis[u]=0;
        for(int i=head[u];i!=-1;i=e[i].nxt)
        {
            int v=e[i].to;
            if(e[i].cap>e[i].flow&&dis[v]>dis[u]+e[i].cost)
            {
                pre[v]=i;
                dis[v]=dis[u]+e[i].cost;
                if(!vis[v])
                {
                    vis[v]=1;
                    q.push(v);
                }
            }
        }
    }
    if(pre[t]==-1)return false;
    return true;
}

void MinCostMaxFlow(int s,int t,int &cost,int &flow)
{
    cost=flow=0;
    while(spfa(s,t))
    {
        int minn=INF;
        for(int i=pre[t];i!=-1;i=pre[e[i^1].to])
        {
            if(minn>e[i].cap-e[i].flow)
                minn=e[i].cap-e[i].flow;
        }
        for(int i=pre[t];i!=-1;i=pre[e[i^1].to])
        {
            e[i].flow+=minn;
            e[i^1].flow-=minn;
            cost+=(e[i].cost*minn);
        }
        flow+=minn;
    }
}

int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        init();
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&a,&b,&x);
            if(x<maze[a][b])
                maze[a][b]=x;
        }
        int s=0,t=2*n+1;
        for(int i=1;i<=n;i++)
        {
            add(s,i,1,0);
            add(i,s,0,0);
            for(int j=1;j<=n;j++)
            {
                if(i==j)continue;
                if(maze[i][j]<INF)
                {
                    add(i,j+n,1,maze[i][j]);
                    add(j+n,i,0,-maze[i][j]);
                }
            }
            add(i+n,t,1,0);
            add(t,i+n,0,0);
        }
        int cost,flow;
        MinCostMaxFlow(s,t,cost,flow);
        printf("%d\n",cost);
    }
    return 0;
}

R->S: 一般图匹配带花树

对一个一般图求最大匹配基本上是n次寻找增广路然而窝并不知道具体是个什么东西orz大概要止于抄模板了

R. Work Scheduling -URAL1099

#include<bits/stdc++.h>
#define maxn 405
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;

int n,s,t,ans,a,b,newbase;
int fa[maxn],base[maxn];
int linker[maxn],maze[maxn][maxn];
bool inque[maxn],inpa[maxn],inblo[maxn];
queue<int>q;

int lca(int u,int v)
{
    memset(inpa,0,sizeof(inpa));
    while(1)
    {
        u=base[u];
        inpa[u]=1;
        if(u==s)break;
        u=fa[linker[u]];
    }
    while(1)
    {
        v=base[v];
        if(inpa[v])break;
        v=fa[linker[v]];
    }
    return v;
}

void reset(int u)
{
    int v;
    while(base[u]!=newbase)
    {
        v=linker[u];
        inblo[base[u]]=inblo[base[v]]=1;
        u=fa[v];
        if(base[u]!=newbase)fa[u]=v;
    }
}

void blossom(int u,int v)
{
    newbase=lca(u,v);
    memset(inblo,0,sizeof(inblo));
    reset(u);
    reset(v);
    if(base[u]!=newbase)fa[u]=v;
    if(base[v]!=newbase)fa[v]=u;
    for(int i=1;i<=n;i++)
    {
        if(inblo[base[i]])
        {
            base[i]=newbase;
            if(!inque[i])q.push(i);
        }
    }
}

void findaug()
{
    memset(inque,0,sizeof(inque));
    memset(fa,0,sizeof(fa));
    for(int i=1;i<=n;i++)
        base[i]=i;
    while(!q.empty())
        q.pop();
    q.push(s);
    inque[s]=1;
    t=0;
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        inque[u]=1;
        for(int v=1;v<=n;v++)
        {
            if(maze[u][v]&&base[u]!=base[v]&&linker[u]!=v)
            {
                if(v==s||(linker[v]>0&&fa[linker[v]]>0))
                    blossom(u,v);
                else if(fa[v]==0)
                {
                    fa[v]=u;
                    if(linker[v]>0)
                    {
                        q.push(linker[v]);
                        inque[linker[v]]=1;
                    }
                    else
                    {
                        t=v;
                        return;
                    }
                }
            }
        }
    }
}

void aug()
{
    int u,v,w;
    u=t;
    while(u>0)
    {
        v=fa[u];
        w=linker[v];
        linker[v]=u;
        linker[u]=v;
        u=w;
    }
}

void solve()
{
    memset(linker,-1,sizeof(linker));
    for(int i=1;i<=n;i++)
    {
        if(linker[i]<0)
        {
            s=i;
            findaug();
            if(t>0)aug();
        }
    }
}

int main()
{
    scanf("%d",&n);
    memset(maze,0,sizeof(maze));
    while((scanf("%d%d",&a,&b))==2)
    //for(int i=0;i<3;i++){scanf("%d%d",&a,&b);
        maze[a][b]=maze[b][a]=1;
    solve();
    ans=0;
    for(int u=1;u<=n;u++)
    {
        if(linker[u]>0)
            ans++;
    }
    printf("%d\n",ans);
    for(int u=1;u<=n;u++)
    {
        if(u<linker[u])
            printf("%d %d\n",u,linker[u]);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/NPU_SXY/article/details/80282845