标签:ems min memset pac 相等 cstring mat \n scanf
题目:http://poj.org/problem?id=3565
首先,我们神奇地发现,没有相交边的匹配可以转化为距离和最小的匹配,所以可以使用KM算法求带全匹配;
要求的是距离和最小,所以把边权转化成负值来求最大;
KM算法有点难理解,看了许多博客,总算朦胧懂了:
首先,每个点有一个“顶标”,用来计算边权和,起初所有左部点的顶标都为相连边的最大边权,右部点的为0;
两个点匹配成功,仅当其顶标和等于其边权(所以可以通过调整顶标来控制边权);
一次dfs没有成功返回0时,我们已经记录下它经过的所有点,这些点都满足“其顶标和等于其边权”条件,组成了一个相等子图;
然而此时的相等子图无法使遍历到的这个左部点得到匹配,所以需要调整顶标;
可知最终的匹配中顶标和等于边权和,为了让顶标和下降最少而又有可能使这个点得到匹配,我们在所有相等子图中的左部点和不在相等子图中的右部点之间找到距离满足条件最小的差值,这样根据它调整顶标后,顶标和下降最少,而又至少会多出这一组可行的点加入相等子图;
这样重复进行,直到这个左部点匹配成功;
最后匹配的权值和是现在的顶标和,这个顶标和比起一开始我们期望的和(每个左部点顶标为其所连最大的边)有所下调,但我们保证每次下调量最少,所以得到最优匹配。
代码如下:
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; double const inf=999999.999999,eps=1e-7; int const MAXN=105; int n,pre[MAXN],ans[MAXN],xa[MAXN],xb[MAXN],ya[MAXN],yb[MAXN]; double dis[MAXN][MAXN],la[MAXN],lb[MAXN]; bool va[MAXN],vb[MAXN]; bool dfs(int x) { va[x]=1; for(int i=1;i<=n;i++) if(!vb[i]&&fabs(la[x]+lb[i]-dis[x][i])<eps) { vb[i]=1; if(!pre[i]||dfs(pre[i])) {pre[i]=x;return 1;} } return 0; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d",&xa[i],&ya[i]); for(int i=1;i<=n;i++) scanf("%d%d",&xb[i],&yb[i]); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dis[i][j]=-sqrt(1.0*(xa[i]-xb[j])*(xa[i]-xb[j])+1.0*(ya[i]-yb[j])*(ya[i]-yb[j])); memset(la,-0x3f,sizeof la); for(int i=1;i<=n;i++) { la[i]=dis[i][1]; for(int j=2;j<=n;j++) la[i]=max(la[i],dis[i][j]); } for(int i=1;i<=n;i++) { while(1) { memset(va,0,sizeof va); memset(vb,0,sizeof vb); if(dfs(i))break; double tmp=inf; for(int i=1;i<=n;i++) if(va[i]) for(int j=1;j<=n;j++) if(!vb[j]) tmp=min(tmp,la[i]+lb[j]-dis[i][j]); for(int i=1;i<=n;i++) { if(va[i])la[i]-=tmp; if(vb[i])lb[i]+=tmp; } } } for(int i=1;i<=n;i++) ans[pre[i]]=i; for(int i=1;i<=n;i++) printf("%d\n",ans[i]); return 0; }
标签:ems min memset pac 相等 cstring mat \n scanf
原文地址:https://www.cnblogs.com/Zinn/p/8870817.html