手机版

ACM必做50题的解题-二分图

发布时间:2024-11-28   来源:未知    
字号:

poj 1325 Machine Schedule(最小点覆盖->二分图最大匹

配)

题意:

有两台机器A和B,分别有n种和m种不同的模式,有k个工作,每个工作都可以在那两个机器的某种特定的模式下处理。如job0既可以在A机器的3好模式下处理,也可以在B机器的4号模式下处理。

机器的工作模式改变只能通过人工来重启。通过改变工作的顺序,和分配每个工作给合适的机器可以减少重启机器的次数达到最小。任务就是计算那个最小的次数。初始时两台机器都运行在0号模式下。

思路:

把每个任务化为一条线,假设任务i在A机器上处理的模式为A[x]点,在B机器上为B[y]点,连接A[x]和B[y],用A机器和B机器中最少的点覆盖所有的边(用最少的模式完成所有的任务)。这是最小点覆盖问题,根据König定理(一个二分图中的最大匹配数等于这个图中的最小点覆盖数)就是求的二分图的最大匹配,然后再用匈牙利算法直接就算出最大匹配数了,要注意的是初始是在0号模式下,所以如果A或B机器其中至少有个在0号模式下时就不用重启机器了,所以建图的时候没有把0建进去。

关于二分匹配在http:///u3/102624/showart.php?id=2060232

#include <stdio.h>

#include <string.h>

#define N 105

#define M 1005

int g[N][N];

int used[N], mat[N], flag[N];

int min, n, m;

/*寻找增广路径*/

int find(int k)

{

int i, j;

for(i=1; i<=g[k][0]; i++)

{

j = g[k][i];

if(!used[j])

{

used[j] = 1;

if(mat[j]==-1 || find(mat[j]))

{

mat[j] = k;

return 1;

}

}

}

return 0;

}

/*匈牙利算法*/

void hungary()

{

int i;

for(i=1; i<=n-1; i++)

{

min += find(i);

memset(used, 0, sizeof(used));

}

printf("%d\n", min);

}

int main()

{

int i, j;

int k, a, b, c;

while(scanf("%d", &n) && n)

{

scanf("%d%d", &m, &k);

memset(mat, -1, sizeof(mat));

memset(g, 0, sizeof(g)); //一开始这个没有初始化,wa了好多次,搞好久 ==! for(i=0; i<k; i++)

{

scanf("%d%d%d", &a, &b, &c);

if(b != 0 && c != 0)

{

g[b][++g[b][0]] = c; //邻接表

}

}

min = 0;

hungary();

}

}

poj 1469 COURSES

二分匹配问题:匈牙利算法实现(可以作为模板)

//二分匹配中,两个集合不能存在交集

#include <iostream>

#include <string.h>

#include <stdio.h>

using namespace std;

#define array1 101

#define array2 301

bool map[array1][array2]; //定义临界矩阵

int final[array2];

int match[array2];

int p,n; //两个集合分别存储p个元素和 n个元素

int DFS(int p) //p为课程

{

int i;

int t;

//遍历所有的学生

for(i=1;i<n+1;++i)

{

if(map[p][i] && !final[i])

{

final[i] = 1;

t= match[i];

match[i]= p; //路径取反操作 (原来在匹配中的边变成不在匹配中,不在的变成在每一次)

if(t==0 || DFS(t)) return 1; //找到了一条增广路径

match[i] = t;

}

}

return 0; //没有找到增广路径

}

int mat()

{

int matchmax = 0; //matchmax为最大匹配数

//遍历所有的课程

for(i=1;i<p+1;++i)

{

memset(final,0,sizeof(final)); //重新标记未访问,此处要注意

if(DFS(i)) matchmax++;

}

return matchmax;

}

int main()

{

//freopen("1.txt","r",stdin);

int t;

int i,j,k;

int temptcount;

int stu;

scanf("%d",&t);

for(i=0;i<t;++i)

{

memset(map,0,sizeof(map)); //对临界矩阵进行初始化

memset(match,0,sizeof(match));

scanf("%d%d",&p,&n);

for(j=1;j<p+1;++j)

{

scanf("%d",&temptcount);

for(k=0;k<temptcount;++k)

{

scanf("%d",&stu);

map[j][stu] = 1;

}

}

if(mat()==p) printf("YES\n");

else printf("NO\n");

}

return 0;

}

POJ 2195 Going Home

二分图最佳匹配,经典的KM算法

#include <stdio.h>

#include <string.h>

const int MAXN = 200+5;

const int INF = 1000000000;

int N;

int g[MAXN][MAXN];

int pre[MAXN];

int lx[MAXN], ly[MAXN], slack[MAXN],man[MAXN][2],ff[MAXN][2];

bool visx[MAXN], visy[MAXN];

int abs(int a){

if(a<0)

return -a;

return a;

}

bool dfs(int t)

{

int i;

visx[t] = true;

for(i = 0; i < N; i++)

{

if(visy[i]) continue;

int tmp = lx[t]+ly[i]-g[t][i];

if(tmp == 0)

{

visy[i] = true;

if(pre[i] == -1 || dfs(pre[i]))

{

pre[i] = t;

return true;

}

}

else if(slack[i] > tmp)

{

slack[i] = tmp;

}

}

return false;

}

int KM()

{

int i, j, k;

for(i = 0; i < N; i++)

lx[i] = -INF;

memset(ly, 0, sizeof(ly));

memset(pre, -1, sizeof(pre));

for(i = 0; i < N; i++)

for(j = 0; j < N; j++)

if(lx[i] < g[i][j])

lx[i] = g[i][j];

for(i = 0; i < N; i++)

{

for(j = 0; j < N; j++) slack[j] = INF;

while(1)

{

memset(visx, false, sizeof(visx));

memset(visy, false, sizeof(visy));

if(dfs(i)) break;

int d = INF;

for(j = 0; j < N; j++)

if(!visy[j] &&

d > slack[j])

d = slack[j];

for(j = 0; j < N; j++)

{

if(visx[j]) lx[j] -= d;

if(visy[j]) ly[j] += d;

else slack[j] -= d;

}

}

}

int ans = 0;

for(i = 0; i < N; i++)

{

if(pre[i] != -1)

ans -= g[pre[i]][i];

}

return ans;

}

int main(){

int h,v,mf,mm;

// freopen("1.txt","r",stdin);

scanf("%d%d",&h,&v);

while(h!=0||v!=0){

mm=mf=0;

for(int i=0;i<h;i++)

for(int j=0;j<v;j++){

char c;

scanf("%c",&c);

while(c!='.'&&c!='H'&&c!='m')

scanf("%c",&c);

if(c=='H'){

ff[mf][0]=i;

ff[mf][1]=j;

mf++;

}

if(c=='m'){

man[mm][0]=i;

man[mm][1]=j;

mm++;

}

}

N=mm;

for(int i=0;i<N;i++)

for(int j=0;j<N;j++)

g[i][j]=-(abs(man[i][0]-ff[j][0])+abs(man[i][1]-ff[j][1]));

printf("%d\n",KM());

scanf("%d%d",&h,&v);

}

}

POJ 2446 Chessboard

算法:

很囧的题,本来打算用搜索过的,发现深搜肯定超时,试了一下,果然超时。然后想到bfs,但是怎么判断状态是否被扩展,hash太麻烦了,并且也不保证不超时,于是又放弃。但是dfs还是有可行的剪枝的,但是实现起来又麻烦,无奈一下,看题解。太佩服牛人了,这样都想到,其实类似的题好像我做过一题,但是忘记了。其实这题的正解是:二分图的匹配。至于怎样匹配,其实很简单,就是一个1*2的方块,其实只能覆盖行和列之和要么为奇数要么为偶数的两个点,那么我们就把各个点划分成这两种,相邻的两边,然后如果存在完全匹配,则输出YES。

代码:

#include<cstdio>

#include<string>

using namespace std;

#define MAX 35

bool a[35][35];

int m,n,k;

int l[MAX*MAX];

int sl;

int root[MAX*MAX];

bool use[MAX*MAX];

int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}};

inline bool can(int i,int j){

return i>=1&&i<=n&&j>=1&&j<=m;

}

bool find(int k){

int x=k/MAX,y=k%MAX;

int i;

for(i=0;i<4;i++){

int sx=x+dir[i][0],sy=y+dir[i][1];

if(can(sx,sy)&&!a[sx][sy]){

int sk=sx*MAX+sy;

if(!use[sk]){

use[sk]=true;

if(root[sk]==-1||find(root[sk])){

root[sk]=k;

return true;

}

}

}

}

return false;

}

void solve(){

int i,j;

sl=0;

for(i=1;i<=n;i++){

for(j=1;j<=m;j++){

if(!a[i][j]&&((i+j)%2==0))l[sl++]=i*MAX+j;

}

}

memset(root,-1,sizeof(root));

int ans=0;

for(i=0;i<sl;i++){

memset(use,false,sizeof(use));

if(find(l[i]))ans++;

}

if(ans==sl)printf("YES\n");

else printf("NO\n");

}

int main(){

scanf("%d%d%d",&n,&m,&k);

int i;

for(i=0;i<k;i++){

int x,y;

scanf("%d%d",&x,&y);

a[x][y]=true;

}

solve();

return 0;

}

poj 1422 Air Raid 最小路径覆盖 模板题

题意:

一个镇里所有的路都是单向路且不会组成回路。

派一些伞兵去那个镇里,要到达所有的路口,有一些或者没有伞兵可以不去那些路口,只要其他人能完成这个任务。每个在一个路口着陆了的伞兵可以沿着街去到其他路口。我们的任务是求出去执行任务的伞兵最少可以是多少个。

思路:

这个题就是个最小路径覆盖问题。

路径覆盖的定义是:在有向图中找一些路径,使之覆盖了图中的所有顶点,就是任意一个顶点都跟那些路径中的某一条相关联,且任何一个顶点有且只有一条路径与之关联,一个单独的顶点是一条路径.最小路径覆盖就是最少的路径覆盖数。

有定理: 最小路径覆盖 = 图的顶点数 – 最大匹配数。

总结:

1.用匈牙利算法求二分匹配,利用最小路径覆盖=顶点总数-最大匹配

2.把每个点拆点为出点和入点

#include<iostream>

using namespace std;

int g[121][121],match[121],mark[121];

int n,m;

int dfs(int i)

{

int j;

for(j=1;j<=n;j++)

if(g[i][j] && !mark[j])

{

mark[j]=1;

if(match[j]==-1 || dfs(match[j]))

{

match[j]=i;

return 1;

}

}

return 0;

}

int main()

{

int s,i,sum,v1,v2;

scanf("%d",&s);

while(s--)

{

scanf("%d%d",&n,&m);

memset(g,0,sizeof(g));

for(i=1;i<=m;i++)

{

scanf("%d%d",&v1,&v2);

g[v1][v2]=1;

}

memset(match,-1,sizeof(match));

sum=0;

for(i=1;i<=n;i++)

{

memset(mark,0,sizeof(mark));

if(dfs(i)) sum++;

}

printf("%d\n",(n-sum));

}

system("pause");

}

POJ 2594* Treasure Exploration 2分匹配

跟1422类似 都是求最小路径覆盖

区别在于这题一个点可以走多次 即建图时要使用可达关系

之前先做一次Floyd再建图即可

#include <iostream>

using namespace std;

bool isconnect[2000][2000]; //邻接矩阵 n m 从1 -- n 1 -- m int mat[2000]; //T中顶点匹配到S中哪个 为匹配时为0 bool ismat[2000]; //T中顶点此次是否已增广

int n,m; //n=|S| m=|T|

bool find(int x)

{

for(int i=1;i<=m;i++)

{

if(isconnect[x][i] && !ismat[i])

{

ismat[i]=1;

if(mat[i]==-1 || find(mat[i]))

{

mat[i]=x;

return 1;

}

}

}

return 0;

}

int biMatch()

{

memset(mat,-1,sizeof(mat));

for(int i=1;i<=n;i++)

{

memset(ismat,0,sizeof(ismat));

result+=find(i);

}

return result;

}

bool adj[600][600];

int main()

{

int rn,st,ed;

while(scanf("%d%d",&n,&rn))

{

memset(adj,0,sizeof(adj));

if(!(n||rn)) break;

for(int i=1;i<=rn;i++)

{

scanf("%d%d",&st,&ed);

adj[st][ed]=1;

}

for(int k=1;k<=n;k++)

for(int i=1;i<=n;i++)

for(int j=1;j<=n;j++)

adj[i][j] = adj[i][j] || (adj[i][k] && adj[k][j]); m=n;

for(int i=1;i<=n;i++)

for(int j=1;j<=n;j++)

isconnect[i][j] = adj[i][j];

printf("%d\n",n-biMatch());

}

return 0;

}

ACM必做50题的解题-二分图.doc 将本文的Word文档下载到电脑,方便复制、编辑、收藏和打印
    ×
    二维码
    × 游客快捷下载通道(下载后可以自由复制和排版)
    VIP包月下载
    特价:29 元/月 原价:99元
    低至 0.3 元/份 每月下载150
    全站内容免费自由复制
    VIP包月下载
    特价:29 元/月 原价:99元
    低至 0.3 元/份 每月下载150
    全站内容免费自由复制
    注:下载文档有可能出现无法下载或内容有问题,请联系客服协助您处理。
    × 常见问题(客服时间:周一到周五 9:30-18:00)