将前两天学的二分图写个博文吧。。
二分图的概念就不讲了,这里只说算法及要注意的地方
PS:有些是在日记上写的,所以不管逻辑啥的,我搬上来了。。
#include <cstdio>
#include <cstring>
using namespace std;
#define FOR(i,a,n) for(i=a;i<=n;++i)
#define CC(a) memset(a,0,sizeof(a))
const int maxn=505;
bool visx[maxn], visy[maxn];
int xm[maxn], ym[maxn]; //xm是x对应的y,ym是y对应的x
int ihead[maxn], inext[maxn*maxn], iv[maxn*maxn], cnt=1;
bool ifind(int x) {
int y, i;
visx[x]=true; //这个用来打印最小覆盖用的
for(i=ihead[x]; i; i=inext[i]) if(!visy[y=iv[i]]) {
visy[y]=true;
if(!ym[y] || ifind(ym[y])) {
ym[y]=x;
xm[x]=y;
return true;
}
}
return false;
}
int main() {
int i, a, b, m, ans, n, n1;
while(~scanf("%d%d%d", &n, &m, &n1)) {
CC(xm); CC(ym); CC(ihead); ans=0; cnt=1;
FOR(i, 1, m) {
scanf("%d%d", &a, &b);
inext[++cnt]=ihead[a]; ihead[a]=cnt; iv[cnt]=b;
}
FOR(i, 1, n1-1) if(!xm[i]) {
CC(visx); CC(visy);
if(ifind(i)) ans++;
}
//输出最小覆盖的方案,最小覆盖的求法
//首先从之前匈牙利树中X集中未盖点再开始用匈牙利拓展,标记访问过的x和y,那么解就是X中未标记的和Y中标记的。
CC(visx); CC(visy);
FOR(i, 1, n1-1) if(!xm[i]) ifind(i);
FOR(i, 1, n1-1) if(!visx[i]) printf("%d ", i);
printf("\n");
FOR(i, n1, n) if(visy[i]) printf("%d ", i);
printf("\n");
printf("max num:%d\n", ans);
}
return 0;
}
这里要注意,二分图必须是完全二分图,即保证X集和Y集的所有元素都匹配。
PS:由于n^3的算法是用bfs,太难写了,暂时我先不写,n^4就这样吧。。
#include <cstdio>
#include <cstring>
using namespace std;
#define FOR(i,a,n) for(i=a;i<=n;++i)
#define CC(a) memset(a,0,sizeof(a))
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
const int maxn=505, oo=~0u>>1;
bool visy[maxn], visx[maxn];
int my[maxn], lx[maxn], ly[maxn];
int ihead[maxn], inext[maxn*maxn], iv[maxn*maxn], iw[maxn*maxn], cnt;
int e;
bool ifind(int x) {
int y, i;
visx[x]=true; //跟着顶标走,只有这样才能保证图的最大权
for(i=ihead[x]; i; i=inext[i]) if(lx[x]+ly[y=iv[i]]==iw[i] && !visy[y]) {
visy[y]=true;
if(!my[y] || ifind(my[y])) {
my[y]=x;
return true;
}
}
return false;
}
void update(int n) {
int i, j, a=oo;
FOR(i, 1, n) if(visx[i]) //从X集访问过的找Y集未访问过的,更新顶标
for(j=ihead[i]; j; j=inext[j]) if(!visy[iv[j]])
a=min(a, lx[i]+ly[iv[j]]-iw[j]);
FOR(i, 1, n) {
if(visx[i]) lx[i]-=a; //顶标更新
if(visy[i]) ly[i]+=a;
}
}
int KM(int n) {
CC(lx); CC(ly); CC(my);
int i, j, ans=0; e=0;
FOR(i, 1, n) for(j=ihead[i]; j; j=inext[j])
lx[i]=max(lx[i], iw[j]);
FOR(i, 1, n) while(1) {
CC(visx); CC(visy);
if(ifind(i)) { e++; break; } else update(n<<1);
}
FOR(i, 1, n) ans+=lx[i];
FOR(i, n+1, n<<1) ans+=ly[i];
return ans;
}
int main() {
int i, a, b, c, n, m, ans;
//PS:我这个自己出的数据很坑啊。。只有X集的个数,所以用邻接表建图的要注意传到KM的n要是2倍大的。。
while(~scanf("%d%d", &n, &m)) {
CC(ihead); cnt=1;
FOR(i, 1, m) {
scanf("%d%d%d", &a, &b, &c);
inext[++cnt]=ihead[a]; ihead[a]=cnt; iv[cnt]=b+n; iw[cnt]=c;
}
ans=KM(n);
printf("max edge:%d\nmax num:%d\n", e, ans);
}
return 0;
}
if(!my[y] || ifind(my[y])) {
my[y]=x;
return true;
}
其实很简单,找增广路的时候,是按照x的顺序下来的,如果x呗找过,那么可以跳过这个点,不找,因而每次调用ifnd的x都是未盖点
从这个未盖点出发,可能走了几条匹配边,但都不会在这个if内判断为真,所以一定会循环到未匹配边上。
1、而到了这个未匹配边上的点,有可能是匹配点也有可能是未盖点,如果是未盖点,那么循环一次就退出了,因为找到了一条增广路了嘛。
如果是匹配点,没关系,下一次循环是my[y],是一条增广路上的x,此时就跳到了上面的步骤1、这里,只不过走的弧一定是未匹配弧。
因此,第一次跑的一定是未匹配边,最后一次跑的也是未匹配边,中间一定是一条增广路的匹配弧。
原文地址:http://www.cnblogs.com/iwtwiioi/p/3832646.html