将前两天学的二分图写个博文吧。。
二分图的概念就不讲了,这里只说算法及要注意的地方
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、这里,只不过走的弧一定是未匹配弧。
因此,第一次跑的一定是未匹配边,最后一次跑的也是未匹配边,中间一定是一条增广路的匹配弧。