KM(带权最大二分匹配) 模板

来源:互联网 发布:黎明杀机刷血点软件 编辑:程序博客网 时间:2024/05/17 07:19

板子:

const int maxn=300+5;int g[maxn][maxn];int link[maxn],lx[maxn],ly[maxn];bool visx[maxn],visy[maxn];int nx,ny,d;bool Find(int x){    visx[x] = true;    for(int i=1;i<=ny;i++){        if(visy[i]) continue;        int tmp = lx[x] + ly[i] - g[x][i];        if(!tmp){            visy[i] = true;            if(link[i] == -1 || Find(link[i])){                link[i] = x;                return true;            }        }        else d=min(d,tmp);    }    return false;}int KM(){    Fill(link,-1);    Fill(ly,0); Fill(lx,0);    for(int i=1;i<=nx;i++){        for(int j=1;j<=ny;j++){            if(g[i][j] > lx[i])                lx[i] = g[i][j];        }    }    for(int i =1 ; i<= nx;i++){        while(true){            Fill(visx,false);            Fill(visy,false);            d = inf;            if(Find(i)) break;            if(d == inf) return -1;            for(int j = 1 ; j<=nx ; j++)                if(visx[j]) lx[j] -= d;            for(int j = 1 ; j<=ny ; j++)                if(visy[j]) ly[j] += d;        }    }    int res = 0;    for(int i=1;i<=ny;i++){        if(link[i] != -1)            res += g[link[i]][i];    }    return res;}void solve(){    int n;    while(~scanf("%d",&n)){        Fill(g,0);        for(int i=1;i<=n;i++){            for(int j=1;j<=n;j++){                scanf("%d",&g[i][j]);            }        }        nx = ny = n;        int res = KM();        printf("%d\n",res);    }}

模板题

基于这道题

const int maxn=300+5;int g[maxn][maxn];     //二分图表示.int link[maxn],lx[maxn],ly[maxn];   //y的匹配关系. x,y的标杆状态.bool visx[maxn],visy[maxn];int nx,ny,d;   //两边的点数. 以及需要寻找的最小的d.bool Find(int x){    visx[x] = true;    for(int i=1;i<=ny;i++){        if(visy[i]) continue;        int tmp = lx[x] + ly[i] - g[x][i];        if(!tmp){   //tmp=0表示在当前图匹配了的边, 即看现在匹配是否合理,            visy[i] = true;            if(link[i] == -1 || Find(link[i])){                link[i] = x;                return true;            }        }        else d=min(d,tmp); //就是在S集合中的x,和不在T集合中的y找一个最小的d.    }    return false;}int KM(){    Fill(link,-1);    Fill(ly,0); Fill(lx,0);    for(int i=1;i<=nx;i++){        for(int j=1;j<=ny;j++){            if(g[i][j] > lx[i])                lx[i] = g[i][j];        }    }    for(int i =1 ; i<= nx;i++){        while(true){            Fill(visx,false);            Fill(visy,false);            d = inf;            if(Find(i)) break;            //若成功(找到了增广轨),则该点增广完成,进入下一个点的增广            //若失败(没有找到增广轨),则需要改变一些点的标号,使得图中可行边的数量增加。            //方法为:将所有在增广轨中(就是在增广过程中遍历到)的X方点的标号全部减去一个常数d,            //所有在增广轨中的Y方点的标号全部加上一个常数d           // if(d == inf) return -1;    //如果可以确定左边的点都会被匹配完,则就可以不用加这条语 //句.    如果不能就要加上这句话. (所以一般在题目中给了一定完美匹配的话,就可以不用这句话) //而有些题目会问你是否是完美匹配,不是的话输出-1,是的话在输出答案,所以这个时候就要加上这句话. //大多数题目是可以不用加的.            for(int j = 1 ; j<=nx ; j++)                if(visx[j]) lx[j] -= d;            for(int j = 1 ; j<=ny ; j++)                if(visy[j]) ly[j] += d;        }    }    int res = 0;    for(int i=1;i<=ny;i++){       // if(link[i] != -1)   //这些都根据题意来加.            res += g[link[i]][i];    }    return res;}    //这道题是保证了一定有最大二分匹配的!!! 所以一些东西可以去掉.void solve(){    int n;    while(~scanf("%d",&n)){        Fill(g,0);   //找最大所以初始化为较小的值. 这样一减就会很小了.        for(int i=1;i<=n;i++){            for(int j=1;j<=n;j++){                scanf("%d",&g[i][j]);            }        }        nx = ny = n;        int res = KM();        printf("%d\n",res);    }}

//这个模型还是比较好看出来, 就是建图时要多想想!

记一下几种转换 : (好好想想)

最小权值匹配, 取反 (所有的边权取反,即取成负的边,最后答案再添个负号就行了,其他步骤一样)没有完备匹配,赋0边权之积最大,取对数(对数相加 就等于指数相乘嘛)