HDU&POJ训练记录3 二分图KM算法
2017-04-12 21:34
459 查看
1、奔小康赚大钱
传送门题意简述
两两匹配有一个权值,求最大权匹配。数据范围
1≤n≤300题解
KM裸题代码
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> using namespace std; #define N 305 const int inf=1000000000; int n,mak,ans; int e ,vis[N<<1],ex ,ey ,link ,lak ; void clear() { mak=ans=0; memset(e,0,sizeof(e));memset(vis,0,sizeof(vis));memset(ex,0,sizeof(ex)); memset(ey,0,sizeof(ey));memset(link,-1,sizeof(link));memset(lak,0,sizeof(lak)); } bool find(int x,int mak) { vis[x]=mak; for (int i=1;i<=n;++i) if (vis[i+n]!=mak) { int tmp=ex[x]+ey[i]-e[x][i]; if (!tmp) { vis[i+n]=mak; if (link[i]==-1||find(link[i],mak)) { link[i]=x; return 1; } } else lak[i]=min(lak[i],tmp); } return 0; } int KM() { int sum=0; for (int i=1;i<=n;++i) for (int j=1;j<=n;++j) ex[i]=max(ex[i],e[i][j]); for (int i=1;i<=n;++i) { for (int j=1;j<=n;++j) lak[j]=inf; while (1) { ++mak; if (find(i,mak)) break; int tmp=inf; for (int k=1;k<=n;++k) if (vis[k+n]!=mak) tmp=min(tmp,lak[k]); for (int k=1;k<=n;++k) { if (vis[k]==mak) ex[k]-=tmp; if (vis[k+n]==mak) ey[k]+=tmp; else lak[k]-=tmp; } } } for (int i=1;i<=n;++i) if (link[i]!=-1) sum+=e[link[i]][i]; return sum; } int main() { while (~scanf("%d",&n)) { clear(); for (int i=1;i<=n;++i) for (int j=1;j<=n;++j) scanf("%d",&e[i][j]); ans=KM(); printf("%d\n",ans); } }
2、Going Home
传送门题意简述
一个二维平面上有H和m,两个点之间的距离为 哈夫曼距离,问H和m都匹配的最小距离。数据范围
1≤n,m≤100题解
最小权匹配问题,所以将二分图的边权都变成相反数,然后最后再变回去代码
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> using namespace std; #define Abs(x) (((x)>0)?-(x):(x)) #define N 105 #define inf 1000000000 char s ; int n,m,cntx,cnty,mak,ans; int xa ,xb ,ya ,yb ; int visx ,visy ,ex ,ey ,e ,link ,lak ; void clear() { cntx=cnty=mak=ans=0; memset(s,0,sizeof(s));memset(xa,0,sizeof(xa));memset(xb,0,sizeof(xb));memset(ya,0,sizeof(ya));memset(yb,0,sizeof(yb)); memset(visx,0,sizeof(visx));memset(visy,0,sizeof(visy));memset(ex,0,sizeof(ex));memset(ey,0,sizeof(ey));memset(e,0,sizeof(e)); memset(link,0,sizeof(link));memset(lak,0,sizeof(lak)); } bool find(int x,int mak) { visx[x]=mak; for (int i=1;i<=n;++i) if (visy[i]!=mak) { int tmp=ex[x]+ey[i]-e[x][i]; if (!tmp) { visy[i]=mak; if (link[i]==-1||find(link[i],mak)) { link[i]=x; return 1; } } else lak[i]=min(lak[i],tmp); } return 0; } int KM() { memset(ex,128,sizeof(ex)); for (int i=1;i<=n;++i) for (int j=1;j<=n;++j) ex[i]=max(ex[i],e[i][j]); memset(link,-1,sizeof(link)); for (int i=1;i<=n;++i) { for (int j=1;j<=n;++j) lak[j]=inf; while (1) { ++mak; if (find(i,mak)) break; int tmp=inf; for (int k=1;k<=n;++k) if (visy[k]!=mak) tmp=min(tmp,lak[k]); for (int k=1;k<=n;++k) { if (visx[k]==mak) ex[k]-=tmp; if (visy[k]==mak) ey[k]+=tmp; else lak[k]-=tmp; } } } int sum=0; for (int i=1;i<=n;++i) if (link[i]!=-1) sum+=e[link[i]][i]; return sum; } int main() { while (~scanf("%d%d",&n,&m)) { if (!n&&!m) break; clear(); for (int i=1;i<=n;++i) { scanf("%s",s+1); for (int j=1;j<=m;++j) if (s[j]=='m') xa[++cntx]=i,xb[cntx]=j; else if (s[j]=='H') ya[++cnty]=i,yb[cnty]=j; } n=cntx; for (int i=1;i<=n;++i) for (int j=1;j<=n;++j) e[i][j]=Abs(xa[i]-ya[j])+Abs(xb[i]-yb[j]); ans=KM(); printf("%d\n",-ans); } }
3、Cyclic Tour
传送门题意简述
给出一个n个点m条边的有向图,每一条边有边权,从这个图中选出若干个环,使每一个点属于且仅属于一个环,并且使环的权值和最小。数据范围
1≤n≤100题解
拆点,然后对于每一条有向边,从左边向右边连边,边权为路径的权值,然后同样是用相反数实现最小匹配这样的话每一个点都会被匹配两次,相当于是一次入点一次出点,一定会形成合法的环
如果有某一个点无法匹配,那么无解
注意这道题有重边,需要取min
代码
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> using namespace std; #define N 105 int n,m,inf,mak,ans; int e ,ex ,ey ,visx ,visy ,link ,lak ; void clear() { mak=ans=0; memset(e,128,sizeof(e));memset(ex,128,sizeof(ex));memset(ey,0,sizeof(ey)); memset(visx,0,sizeof(visx));memset(visy,0,sizeof(visy));memset(link,-1,sizeof(link));memset(lak,0,sizeof(lak)); inf=e[0][0]; } bool find(int x,int mak) { visx[x]=mak; for (int i=1;i<=n;++i) if (visy[i]!=mak) { int tmp=ex[x]+ey[i]-e[x][i]; if (!tmp) { visy[i]=mak; if (link[i]==-1||find(link[i],mak)) { link[i]=x; return 1; } } else lak[i]=min(lak[i],tmp); } return 0; } int KM() { for (int i=1;i<=n;++i) for (int j=1;j<=n;++j) ex[i]=max(ex[i],e[i][j]); for (int i=1;i<=n;++i) { for (int j=1;j<=n;++j) lak[j]=-inf; while (1) { ++mak; if (find(i,mak)) break; int tmp=-inf; for (int k=1;k<=n;++k) if (visy[k]!=mak) tmp=min(tmp,lak[k]); for (int k=1;k<=n;++k) { if (visx[k]==mak) ex[k]-=tmp; if (visy[k]==mak) ey[k]+=tmp; else lak[k]-=tmp; } } } int sum=0; for (int i=1;i<=n;++i) if (e[link[i]][i]==inf) return -1; else sum+=e[link[i]][i]; return -sum; } int main() { while (~scanf("%d%d",&n,&m)) { clear(); for (int i=1;i<=m;++i) { int x,y,z;scanf("%d%d%d",&x,&y,&z); e[x][y]=max(e[x][y],-z); } ans=KM(); printf("%d\n",ans); } }
4、Interesting Housing Problem
传送门题意简述
有n个学生,m个房间,每一个学生对一些房间有一个评分,求使所有的学生每一个人分配一个房间并使评分最大注意这道题学生不会分配一个没有评分或者评分为负数的房间
数据范围
1≤n,m≤500题解
KM求最大匹配即可最后统计答案的时候需要统计一下共匹配了多少个学生
代码
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> using namespace std; #define N 505 int T,n,m,inf,cnt,mak,ans; int e ,ex ,ey ,visx ,visy ,link ,lak ; void clear() { mak=ans=0; memset(e,0,sizeof(e));memset(ex,0,sizeof(ex));memset(ey,0,sizeof(ey)); memset(visx,0,sizeof(visx));memset(visy,0,sizeof(visy)); memset(link,0,sizeof(link));memset(lak,0,sizeof(lak)); } bool find(int x,int mak) { visx[x]=mak; for (int i=1;i<=m;++i) if (visy[i]!=mak) { int tmp=ex[x]+ey[i]-e[x][i]; if (!tmp) { visy[i]=mak; if (link[i]==-1||find(link[i],mak)) { link[i]=x; return 1; } } else lak[i]=min(lak[i],tmp); } return 0; } int KM() { memset(ex,128,sizeof(ex)); for (int i=1;i<=n;++i) for (int j=1;j<=m;++j) ex[i]=max(ex[i],e[i][j]); memset(link,-1,sizeof(link)); for (int i=1;i<=n;++i) { for (int j=1;j<=m;++j) lak[j]=inf; while (1) { ++mak; if (find(i,mak)) break; int tmp=inf; for (int k=1;k<=m;++k) if (visy[k]!=mak) tmp=min(tmp,lak[k]); for (int k=1;k<=n;++k) if (visx[k]==mak) ex[k]-=tmp; for (int k=1;k<=m;++k) if (visy[k]==mak) ey[k]+=tmp; else lak[k]-=tmp; } } int sum=0,flag=0; for (int i=1;i<=m;++i) if (link[i]!=-1&&e[link[i]][i]!=-inf) sum+=e[link[i]][i],++flag; if (flag<n) return -1; return sum; } int main() { while (~scanf("%d%d%d",&n,&m,&cnt)) { clear(); memset(e,128,sizeof(e));inf=-e[0][0]; for (int i=1;i<=cnt;++i) { int x,y,z;scanf("%d%d%d",&x,&y,&z); if (z<0) continue; ++x,++y;e[x][y]=z; } ans=KM(); printf("Case %d: %d\n",++T,ans); } }
5、Assignment
传送门题意简述
有n个人和m个任务,每一个人要分配到一个任务,每一个人分配到一个任务会有一个价值。现已知道一个初始的分配任务的方案,要求对这种方案进行调整,使在价值最大的前提下,每个人的任务修改的个数最少。
数据范围
1≤n≤m≤50,1≤ai,j≤10000题解
感觉这种既要保证价值最大又要保证修改个数最少的题目有一个套路,就是先讲原先的权值扩大很多倍,比如扩大100倍,然后对于原先的那种方案,在对应的位置上加1,然后做最大权值匹配这样的话,不修改的个数应该是ans%100,最大价值应该是ans/100
其实这是很容易理解的,在原先的方案上加1就是让这条边尽量匹配
代码
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> using namespace std; #define N 55 #define inf 1000000000 int n,m,last,ans,mak; int e ,ex ,ey ,visx ,visy ,link ,lak ; void clear() { last=ans=mak=0; memset(e,0,sizeof(e));memset(ex,0,sizeof(ex));memset(ey,0,sizeof(ey)); memset(visx,0,sizeof(visx));memset(visy,0,sizeof(visy));memset(link,0,sizeof(link));memset(lak,0,sizeof(lak)); } bool find(int x,int mak) { visx[x]=mak; for (int i=1;i<=m;++i) if (visy[i]!=mak) { int tmp=ex[x]+ey[i]-e[x][i]; if (!tmp) { visy[i]=mak; if (link[i]==-1||find(link[i],mak)) { link[i]=x; return 1; } } else lak[i]=min(lak[i],tmp); } return 0; } int KM() { for (int i=1;i<=n;++i) for (int j=1;j<=m;++j) ex[i]=max(ex[i],e[i][j]); memset(link,-1,sizeof(link)); for (int i=1;i<=n;++i) { for (int j=1;j<=m;++j) lak[j]=inf; while (1) { ++mak; if (find(i,mak)) break; int tmp=inf; for (int k=1;k<=m;++k) if (visy[k]!=mak) tmp=min(tmp,lak[k]); for (int k=1;k<=n;++k) if (visx[k]==mak) ex[k]-=tmp; for (int k=1;k<=m;++k) if (visy[k]==mak) ey[k]+=tmp; else lak[k]-=tmp; } } int sum=0; for (int i=1;i<=m;++i) if (link[i]!=-1) sum+=e[link[i]][i]; return sum; } int main() { while (~scanf("%d%d",&n,&m)) { clear(); for (int i=1;i<=n;++i) for (int j=1;j<=m;++j) scanf("%d",&e[i][j]),e[i][j]*=100; for (int i=1;i<=n;++i) { int x;scanf("%d",&x); last+=e[i][x]/100; ++e[i][x]; } ans=KM(); printf("%d %d\n",n-ans%100,ans/100-last); } }
6、Ants
传送门题意简述
在一个二维平面上给出n个黑点和n个白点,求一种方案使黑点白点配对形成的n条线段不相交。数据范围
1≤n≤100,−10000≤x,y≤10000题解
假设我们现在有2个黑点和2个白点要配对的话那么一定有一种方案两条线段相交,另一种方案两条线段不相交,容易证明相交的时候两条线段的长度一定大于不相交的方案所以黑白两点之间的边权就是距离,然后做二分图的最小权值匹配就行了
注意距离必须是严格的欧式距离,如果不开平方的话会出现问题
代码
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> using namespace std; #define N 105 const double eps=1e-9; const double inf=1e9; int n,m,mak; double e ,ex ,ey ,lak ; int visx ,visy ,link ; struct data{double x,y;}a ,b ; double qr(double x) {return x*x;} bool find(int x,int mak) { visx[x]=mak; for (int i=1;i<=n;++i) if (visy[i]!=mak) { double tmp=ex[x]+ey[i]-e[x][i]; if (tmp<=eps&&tmp>=-eps) { visy[i]=mak; if (link[i]==-1||find(link[i],mak)) { link[i]=x; return 1; } } else lak[i]=min(lak[i],tmp); } return 0; } void KM() { memset(ex,128,sizeof(ex)); for (int i=1;i<=n;++i) for (int j=1;j<=n;++j) ex[i]=max(ex[i],e[i][j]); memset(link,-1,sizeof(link)); for (int i=1;i<=n;++i) { for (int j=1;j<=n;++j) lak[j]=inf; while (1) { ++mak; if (find(i,mak)) break; double tmp=inf; for (int k=1;k<=n;++k) if (visy[k]!=mak) tmp=min(tmp,lak[k]); for (int k=1;k<=n;++k) { if (visx[k]==mak) ex[k]-=tmp; if (visy[k]==mak) ey[k]+=tmp; else lak[k]-=tmp; } } } } int main() { scanf("%d",&n); for (int i=1;i<=n;++i) scanf("%lf%lf",&b[i].x,&b[i].y); for (int i=1;i<=n;++i) scanf("%lf%lf",&a[i].x,&a[i].y); for (int i=1;i<=n;++i) for (int j=1;j<=n;++j) e[i][j]=-sqrt(qr(a[i].x-b[j].x)+qr(a[i].y-b[j].y)); KM(); for (int i=1;i<=n;++i) printf("%d\n",link[i]); }
相关文章推荐
- POJ 2195&&HDU 1533 Going Home(KM算法解决二分图最小权匹配)
- HDU&POJ训练记录4 生成函数
- Poj 3692 & Hdu 2458 (08 合肥Online 二分图 最大团)
- hdu 1829 &poj 2492 A Bug's Life(推断二分图、带权并查集)
- hdu 1829 &poj 2492 A Bug's Life(判断二分图、带权并查集)
- [ACM] POJ 3686 The Windy's (二分图最小权匹配,KM算法,特殊建图)
- hdoj 2255 奔小康赚大钱 (KM算法 详解+模板) && HDU 1533 Going Home (二分图最小权匹配 KM模板)纯模板
- 二分图的拓展与应用:HDU 2819&&POJ 1486&&HDU 3488&&HDU1853
- HDU 1350 & HDU 1960 & POJ 2060 Taxi Cab Scheme【二分图之最小路径覆盖,经典】
- POJ 1904 King's Quest && HDU 4685 Prince and Princess (强联通解决二分图可行匹配问题)
- (2017多校训练第二场)HDU - 6055 & POJ - 2002 Regular polygon 哈希
- hdu 1829&&poj 2492 A Bug's Life(判二分图)
- Hdu 1892&&Poj 2492 A Bug's Life[判断二分图 || 种类并查集]
- 二分图的基础与基本应用:POJ 1469&&POJ 3041&&HDU 2255&&HDU1533
- POJ 1177 Picture & hdu 1828 Picture(扫描线)
- 二分图KM算法 HDU 2255
- poj 2195 二分图最优匹配 KM算法求最小值
- poj 3686 The Windy's 二分匹配 KM算法求最小权匹配
- Hdu 1829 A Bug's Life && Poj 1182 食物链 (并查集偏移量的应用)
- HDU_1455 && POJ_1011 Sticks (dfs)