二分图专题

Fire Net hdu1045

题意:在一个最多4×4的方格中,类似于求棋盘问题,只是有些点变成了“墙”,对于同一行或同一列如果有墙,墙两边的点是没有影响的。

思路:对于普通的棋盘问题,只需要把每一行或每一列当成一个点建图就可以了,但这个因为有墙的存在显然不行了,

可以想到对于普通的棋盘问题在没有墙的时候对于每一行每一列正好是一行一个点,一列一个点,所以也就是连续的 “.” 当成一个点,所以对于这个题也是,对于每一行或者每一列,依然把连续的 " . "当做一个点,直到遇到墙。

这样处理完后利用重新形成的点建图,再跑一个匈牙利就可以了。

#include<bits/stdc++.h>
#define exp 1e-8
#define mian main
#define pii pair<int,int>
#define pll pair<ll,ll>
#define ll long long
#define pb push_back
#define PI  acos(-1.0)
#define inf 0x3f3f3f3f
#define w(x) while(x--)
#define int_max 2147483647
#define lowbit(x) (x)&(-x)
#define gcd(a,b) __gcd(a,b)
#define pq(x)  priority_queue<x>
#define ull unsigned long long
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define pl(a,n) next_permutation(a,a+n)
#define ios ios::sync_with_stdio(false)
#define met(a,x) memset((a),(x),sizeof((a)))
using namespace std;
char a[20][20];
int n,e[20][20],cnt_row,cnt_col,row[20][20],col[20][20];
bool used[20];
int cy[20],cx[20];
bool line(int x)
{
    for(int i=1;i<=cnt_col;i++){
        if(!used[i]&&e[x][i]){
            used[i]=1;
            if(cy[i]==-1||line(cy[i])){
                cy[i]=x;
                return true;
            }
        }
    }
    return false;
}
int maxmatch()
{
    int ans=0;
    met(cy,-1);
    met(cx,-1);
    for(int i=1;i<=cnt_row;i++){
        met(used,0);
        ans+=line(i);
    }
    return ans;
}
int main()
{
    while(~scanf("%d",&n)&&n){
            met(a,0);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
               cin>>a[i][j];
               met(e,0);
                cnt_row=0;
                cnt_col=0;
                met(row,0);
                met(col,0);
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            if(a[i][j]=='.'&&row[i][j]==0){ //处理每一行
                cnt_row++;
                for(int k=j;k<=n&&a[i][k]=='.';k++)  //连续的点赋为一个值
                    row[i][k]=cnt_row;
            }
            if(a[i][j]=='.'&&col[i][j]==0){//处理每一列
                cnt_col++;
                for(int k=i;k<=n&&a[k][j]=='.';k++)
                    col[k][j]=cnt_col;
            }
        }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(a[i][j]=='.')  //当点是"."把行和列两个点连成一个新边
            e[row[i][j]][col[i][j]]=1;
        int ans=maxmatch(); //二分匹配
        printf("%d\n",ans);
    }
}

HDU 2444 The Accomodation of Students(BFS/DFS判断二分图+最大匹配)

题意:有n个人,m种关系,分别表示两两是认识的,关系不能传递,问能否把这些人分成两部分,每一部分的人互相都不认识,如果可以,再问他们上述互相认识的两人住一个房间,问最多可以分多少个房间?

思路:

题目的题意暗示的已经很明显了,分成两部分,分别都不认识,很明显就是要分成一个二分图,这时就要判断这些关系能否形成一个二分图,这里可以用BFS染色法(dfs也可),随便选一个点开始bfs,把这个点先标记为1,然后与他相连的点标记为0,依次这样标记,如果存在某个点与他相连的点标记相同那么就不能形成二分图(可以自己画图看一看)。

然后就可以用匈牙利了,一开始我分别把标记为1和0的存在了两个数组,然后匹配,但WA了,最后改成把这n个点拆点,得出的匹配除2才AC的,求大佬解答一下为什么前面那种做法不行......orz

#include<bits/stdc++.h>
#define exp 1e-8
#define mian main
#define pii pair<int,int>
#define pll pair<ll,ll>
#define ll long long
#define pb push_back
#define PI  acos(-1.0)
#define inf 0x3f3f3f3f
#define w(x) while(x--)
#define int_max 2147483647
#define lowbit(x) (x)&(-x)
#define gcd(a,b) __gcd(a,b)
#define pq(x)  priority_queue<x>
#define ull unsigned long long
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define pl(a,n) next_permutation(a,a+n)
#define ios ios::sync_with_stdio(false)
#define met(a,x) memset((a),(x),sizeof((a)))
using namespace std;
const int N=1e5+10;
struct node
{
    int v,ne;
}e[N];
int ee[510][510];
int head[N];
int a[510],b[510];
int tot,n,m,tot1,tot2;
int vis[510],cy[510];
bool used[510];
bool flag;
bool bfs(int x) //bfs判断二分图
{
    queue<int>q;
    q.push(x);
    vis[x]=1;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        int k=vis[u];
        int kk=vis[u]==1?0:1;
        for(int i=head[u];i;i=e[i].ne){
            int v=e[i].v;
            if(vis[v]==k)
                return false;
            if(vis[v]==-1){
                vis[v]=kk;
                q.push(v);
                }
        }
    }
    return true;
}
/*void dfs(int x) //dfs判断二分图
{
    if(flag)
        return ;
    int k=vis[x];
    int kk=vis[x]==1?0:1;
    for(int i=head[x];i;i=e[i].ne){
        int v=e[i].v;
        if(vis[v]==-1){
            vis[v]=kk;
            dfs(v);
        }
        else{
            if(vis[v]==k){
                flag=true;
                break;
            }
        }
    }
}*/
void add(int x,int y)
{
    e[++tot].ne=head[x];
    e[tot].v=y;
    head[x]=tot;
}
void init()
{
    met(head,0);
    tot=0;
    met(a,0);
    met(b,0);
    met(ee,0);
    met(vis,-1);
}
bool line(int x)
{
    for(int i=1;i<=n;i++){
        if(!used[i]&&ee[x][i]){
            used[i]=1;
            if(cy[i]==-1||line(cy[i])){
                cy[i]=x;
                return true;
            }
        }
    }
    return false;
}
int maxmatch()
{
    int ans=0;
    met(cy,-1);
    for(int i=1;i<=n;i++){
        met(used,0);
        ans+=line(i);
    }
    return ans;
}
int main()
{
    while(~scanf("%d%d",&n,&m)){
            init();
        int x,y;
        int t;
        for(int i=1;i<=m;i++){
            scanf("%d%d",&x,&y);
            t=x;
            ee[x][y]=1;
            ee[y][x]=1;
            add(x,y);
            add(y,x);
        }
        if(bfs(t)){
            printf("%d\n",maxmatch()/2);
        }
        else
            printf("No\n");
    }
}

Swap HDU - 2819

题意:给一个N*N的矩阵,值只能是1或0,问能否经过多次交换行和列使对角线上全为1?

若能,给出交换策略、不能 输出-1

思路:

首先分析什么时候可以交换成功,如果任意一行或任意一列全为0,那么肯定就不行,即只要每一行和每一列都有1就肯定可以

但如果只是这样的话,为什么要二分图呢?因为还要给出怎样交换的,这个时候匈牙利算法中顺带求出的用来储存哪个点和哪个点匹配的数组就有用处了,跑完一遍匈牙利后,根据得出的数组,对于n行依次遍历(为什么可以依次遍历?大家可以画画图想一下,题目虽说行和列都可交换,但其实只需只交换行或者只交换列即可),先判断每一行的点匹配的是否等于这一行的序号(即第几行),如果不等于,找出需要和哪一行换,把要交换的信息存到数组中即可。

#include<bits/stdc++.h>
#define exp 1e-8
#define mian main
#define pii pair<int,int>
#define pll pair<ll,ll>
#define ll long long
#define pb push_back
#define PI  acos(-1.0)
#define inf 0x3f3f3f3f
#define w(x) while(x--)
#define int_max 2147483647
#define lowbit(x) (x)&(-x)
#define gcd(a,b) __gcd(a,b)
#define pq(x)  priority_queue<x>
#define ull unsigned long long
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define pl(a,n) next_permutation(a,a+n)
#define ios ios::sync_with_stdio(false)
#define met(a,x) memset((a),(x),sizeof((a)))
using namespace std;
const int N=110;
int e[N][N];
int n;
bool used[N];
int cx[N],cy[N];
int a[N],b[N];
bool line(int x)
{
    for(int i=1;i<=n;i++){
        if(!used[i]&&e[x][i]){
            used[i]=1;
            if(cy[i]==-1||line(cy[i])){
                cy[i]=x;
                cx[x]=i;
                return true;
            }
        }
    }
    return false;
}
int maxmatch()
{
    int ans=0;
    met(cx,-1);
    met(cy,-1);
    for(int i=1;i<=n;i++){
        met(used,0);
        ans+=line(i);
    }
    return ans;
}
int main()
{
    while(~scanf("%d",&n)){
            met(e,0);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                scanf("%d",&e[i][j]);
            int num=maxmatch();
            if(num==n){  //最大匹配等于N才可以
                int ans=0;
                for(int i=1;i<=n;i++){
                        int k=cx[i];//这一行匹配的是第几列
                        int kk=cy[i];//第I行要匹配的那一列在第几行
                        if(k==i)
                            continue;
                            a[++ans]=i;
                            b[ans]=kk;
                        swap(cx[i],cx[kk]);
                        swap(cy[i],cy[k]);
                }
                printf("%d\n",ans);
                for(int i=1;i<=ans;i++)
                    printf("R %d %d\n",a[i],b[i]);
            }
            else printf("-1\n");
    }
}

奔小康赚大钱

题意:一道模板题,n个村民分配房子,房子正好也是n个,每个村民对不同的房子会出不同的价格,问最大可以获得多大的利润?

思路:详解可以参照我转载的一篇博客,另外我的代码里的注释是学习时看的博客上举的例子的注释,读者可以自行把那些男生,女生换成村民和房子(逃...

#include<bits/stdc++.h>
#define exp 1e-8
#define mian main
#define pii pair<int,int>
#define pll pair<ll,ll>
#define ll long long
#define pb push_back
#define PI  acos(-1.0)
#define inf 0x3f3f3f3f
#define w(x) while(x--)
#define int_max 2147483647
#define lowbit(x) (x)&(-x)
#define gcd(a,b) __gcd(a,b)
#define pq(x)  priority_queue<x>
#define ull unsigned long long
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define pl(a,n) next_permutation(a,a+n)
#define ios ios::sync_with_stdio(false)
#define met(a,x) memset((a),(x),sizeof((a)))
using namespace std;
const int N=310;
int n;
int e[N][N]; //关系
int ex_girl[N];//女生的期望值
int ex_boy[N];//男生的期望值
int match[N]; //记录每个男生匹配到的女生,如果没有为-1
bool girl[N];//标记每一轮匹配过的女生
bool boy[N];//标记每一轮匹配过的男生
int slack[N];//记录每个男生要被女生选中还需多少期望值
bool line(int x)
{
    girl[x]=1;
    for(int i=1;i<=n;i++){
        if(boy[i])
            continue;
        int k=ex_boy[i]+ex_girl[x]-e[x][i];
        if(k==0){ //符合要求
            boy[i]=1;
            if(match[i]==-1||line(match[i])){
                match[i]=x;
                return true;
            }
        }
        else slack[i]=min(slack[i],k);
    }
    return false;
}
int km()
{
    met(ex_boy,0);
    met(match,-1);
    for(int i=1;i<=n;i++){
        ex_girl[i]=e[i][1];
        for(int j=2;j<=n;j++)
            ex_girl[i]=max(ex_girl[i],e[i][j]);  //初始每个女生的期望值为对每个男生的期望的最大值
    }
    for(int i=1;i<=n;i++){  //为每一个女生匹配
        met(slack,inf);
        while(1){
            met(girl,0);
            met(boy,0);
            if(line(i))  //匹配成功,退出
                break;
            int d=inf; //匹配失败
            for(int j=1;j<=n;j++)
                if(!boy[j])
                d=min(d,slack[j]);  //最小可降低的期望值
            for(int j=1;j<=n;j++){
                if(girl[j])
                    ex_girl[j]-=d;  //所以找到过的女生降低期望值
                if(boy[j])
                    ex_boy[j]+=d; //所有找到过的男生增加期望值
                else slack[j]-=d; //没有访问过的boy 因为girl们的期望值降低,距离得到女生倾心又进了一步!
            }
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        ans+=e[match[i]][i];
    return ans;
}
int main()
{
    while(~scanf("%d",&n)){
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            scanf("%d",&e[i][j]);
        printf("%d\n",km());
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章