1. 程式人生 > >二分圖匹配+例題poj1469(模板題)

二分圖匹配+例題poj1469(模板題)

給定一個二分圖G(無向圖),在G的一個子圖M中,M的邊集中的任意兩條邊都不依附於同一個頂點,則稱M是一個匹配.

       選擇這樣的邊數最大的子集稱為圖的最大匹配問題(maximal matchingproblem)

       如果一個匹配中,圖中的每個頂點都和圖中某條邊相關聯,則稱此匹配為完全匹配,也稱作完備匹配。

       如果該二分圖的每條邊都有一個權值且存在完備匹配,那麼我們要找出一個所有邊權值和最大的完備匹配的問題叫做二分圖的最優匹配問題。

       (在下面給出了求二分圖最大匹配和最優匹配的模板程式碼)

       二分圖的最小覆蓋數:在二分圖中選取最少數目的點集,使得二分圖任意一邊都至少有一個端點在該點集中。這個點集的大小是二分圖的最小覆蓋數,且二分圖的最小覆蓋數==二分圖的最大匹配數。

       二分圖的最大獨立集:在二分圖中選取最多數目的點集,使得該點集中的任意兩點在二分圖中都不存在一條邊相連。這個點集就是二分圖的最大獨立集。且二分圖的最大獨立集大小==|G|(二分圖頂點數) - 二分圖最大匹配數。

 下面是求二分圖最大匹配的模板

//二分圖最大匹配模板,二分圖都是無向圖
//呼叫下面演算法前,保證本圖是二分圖
/*************vecotr模板*****************/
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=100+5;
 
struct Max_Match
{
    int n,m;//左右點集大小,點從1開始編號
    vector<int> g[maxn];//g[i]表示左邊第i個點鄰接的右邊點的集合
    bool vis[maxn];//vis[i]表示右邊第i個點是否在本次match中被訪問過
    int left[maxn];//left[i]==j表右邊第i個點與左邊第j個點匹配,為-1表無點匹配
 
    void init(int n,int m)
    {
        this->n=n;
        this->m=m;
        for(int i=1;i<=n;i++) g[i].clear();
        memset(left,-1,sizeof(left));
    }
 
    //判斷從左u點是否可以找到一條增廣路
    bool match(int u)
    {
        for(int i=0;i<g[u].size();i++)
        {
            int v=g[u][i];
            if(!vis[v])
            {
                vis[v]=true;
                if(left[v]==-1 || match(left[v]))//找到增廣路
                {
                    left[v]=u;
                    return true;
                }
            }
        }
        return false;
    }
 
    //返回當前二分圖的最大匹配數
    int solve()
    {
        int ans=0;//最大匹配數
        for(int i=1;i<=n;i++)//每個左邊的節點找一次增廣路
        {
            memset(vis,0,sizeof(vis));
            if(match(i)) ans++;//找到一條增廣路,形成一個新匹配
        }
        return ans;
    }
}MM;
/*************vecotr模板*****************/

例題。。。。。。

題意:

       有P門課和N個學生,每門課可能有0個或多個學生選修想選.現在問你能不能找到一種選課方案,使得P門課每門課都正好只有1個學生選修,且任意兩個選了課的學生所選的課都不同?

分析:

注意:二分圖是無向圖,但是二分圖中的g陣列只儲存從左邊點集到右邊點集的邊.即g[i]==j,只代表左邊i點與右邊j點連線了一條無向邊,不代表右邊i點也與左邊j點連了邊. 以上結論一定要牢記.

       因為只有課與學生之間存在邊,所以該圖可以看成是左邊有P個點,右邊有N個點的二分圖.

       又由於我們想使得課與學生連的邊最多且每個學生只能連一門課,每門課也只能有一個學生連線(即每個學生或每門課最多隻有一條邊依附).所以這就是一個匹配問題且是最大匹配.

       現在要求這個圖的最大匹配,看看能否該最大匹配邊數目==P.
poj1469--->請點這裡

程式碼:
 

#include<cstdio>
#include<cstring>
using namespace std;
 
struct Max_Match
{
    int p,n;//左右兩點集的點個數
    bool g[110][310];//鄰接矩陣,g[i][j]=true表示從左邊第i個節點到右邊第j個節點有邊
    int left[310];//left[i]==j表右邊第i個點與左邊第j個點匹配,為-1表無點匹配
    bool vis[310];//表右邊第i個點是否已經被訪問過
 
    void init()
    {
        memset(g,0,sizeof(g));
        memset(left,-1,sizeof(left));
    }
 
    bool match(int i)
    {
        for(int j=1;j<=n;j++)if(g[i][j] && !vis[j])
        {
            vis[j]=true;
            if(left[j]==-1 || match(left[j]) )
            {
                left[j]=i;
                return true;
            }
        }
        return false;
    }
 
    int solve()
    {
        int ans=0;//記錄匹配邊數目
        for(int i=1;i<=p;i++)
        {
            memset(vis,0,sizeof(vis));
            if(match(i)) ans++;
        }
        return ans;
    }
}MM;
 
int main()
{
    int T; scanf("%d",&T);
    while(T--)
    {
        MM.init();
        bool ok=true;
        scanf("%d%d",&MM.p,&MM.n);
        for(int i=1;i<=MM.p;i++)
        {
            int num;
            scanf("%d",&num);
            if(num)
            {
                while(num--)
                {
                    int j;
                    scanf("%d",&j);
                    MM.g[i][j]=true;
                }
            }
            else ok=false;
        }
 
        if(ok) printf("%s\n",MM.solve()==MM.p?"YES":"NO");
        else printf("NO\n");
    }
    return 0;
}