最小乘积生成树和最小乘积最大匹配
两个知识的本质是一样的。都是每条边有k个权值(一般k为2),现在要取一个边集M使得其将所有点连通,并使每一种边权的总和的乘积最小。不同的是一个是生成树一个是匹配。
对于这一类问题,我们都可以把每种方案的x之和与y之和作为它的坐标(x,y)
要让乘积最小,那么可能的方案的坐标一定在一个下凸壳上。
首先我们求出x最小的方案的坐标,再求出y最小方案的坐标
这就是凸壳的两个端点A,B。
然后考虑分治,每次找出离直线AB最远的点C,再继续处理
要使距离最远,就是使向量AB和向量AC的叉积最大
即最大化(c.x-a.x)*(b.y-a.y)-(c.y-a.y)*(b.x-a.x)
即c.x*(b.y-a.y)+c.y*(a.x-b.x) -a.x*(b.y-a.y)+a.y*(b.x-a.x)
后面的一部分是常数,不用管。
就是要使c.x*(b.y-a.y)+c.y*(a.x-b.x) 最大化
对于生成树来说就用kruscal算法,匹配则是KM算法确定c,然后对AC、CB递归做同样的过程,直到找不到一个在左下的点C为止。
最小乘积生成树算法:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <queue> #include <vector> using namespace std; #define N 205 #define M 10100 #define inf 0x3f3f3f3f struct Edge { int u,v; int a,b; int c; void read(){ scanf("%d%d%d%d",&u,&v,&a,&b); u++; v++; } }e[M]; bool cmpa(const Edge &a,const Edge &b){ return a.a<b.a; } bool cmpb(const Edge &a,const Edge &b){ return a.b<b.b; } bool cmpc(const Edge &a,const Edge &b){ return a.c<b.c; } struct Point{ int x,y; void print(){ printf("%d %d\n",x,y); } Point(int _x=0,int _y=0):x(_x),y(_y){} bool operator < (const Point &A) const { unsigned int p=x; p*=y; unsigned int q=A.x; q*=A.y; return p==q?x<A.x:p<q; } }ans,now,mina,minb; int f[N],n,m; int find(int x){ return f[x]==x?f[x]:f[x]=find(f[x]); } Point kruscal() { int i,fa,fb; now=Point(0,0); for(int i=1;i<=n;i++) f[i]=i; for(int i=1;i<=m;i++){ fa=find(e[i].u); fb=find(e[i].v); if(fa!=fb){ f[fb]=fa; now.x+=e[i].a; now.y+=e[i].b; } } if(now<ans) ans=now; return now; } int xmul(const Point &A,const Point &B,const Point &C) {return (C.y-A.y)*(B.x-A.x)-(C.x-A.x)*(B.y-A.y);} void work(const Point &a,const Point &b) { for(int i=1;i<=m;i++){ e[i].c=e[i].b*(a.x-b.x)+e[i].a*(b.y-a.y); } sort(e+1,e+m+1,cmpc); Point c=kruscal(); if(xmul(a,b,c)<=0) return; work(a,c); work(b,c); } int main() { // freopen("test.in","r",stdin); int i,j,k; int a,b,c; ans=Point(inf,inf); scanf("%d%d",&n,&m); for(i=1;i<=m;i++)e[i].read(); sort(e+1,e+m+1,cmpa),mina=kruscal(); sort(e+1,e+m+1,cmpb),minb=kruscal(); work(minb,mina),ans.print(); return 0; }
最小乘积匹配:
#include<iostream> #include<cstring> #include<cstdio> #define inf 0x7fffffff struct poi{int x,y;}le,ri; int lx[75],ly[75],sla[75]; int g[75][75],a[75][75],b[75][75],f[75]; int n; bool vx[75],vy[75]; bool operator ==(poi a,poi b){return a.x==b.x&&a.y==b.y;} using namespace std; bool dfs(int x) { vx[x]=true; for (int y=1; y<=n; y++) { if (!vy[y]) { int t=lx[x]+ly[y]-g[x][y]; if (!t) { vy[y]=true; if (!f[y]||dfs(f[y])) { f[y]=x; return true; } } else sla[y]=min(sla[y],t); } } return false; } poi km() { memset(lx,0,sizeof(lx)); memset(ly,0,sizeof(ly)); memset(f,0,sizeof(f)); for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) lx[i]=max(lx[i],g[i][j]); for (int x=1; x<=n; x++) { memset(sla,63,sizeof(sla)); while (true) { memset(vx,0,sizeof(vx)); memset(vy,0,sizeof(vy)); if (dfs(x)) break; int d=inf; for (int i=1; i<=n; i++) if (!vy[i]) d=min(d,sla[i]); for (int i=1; i<=n; i++) { if (vx[i]) lx[i]-=d; if (vy[i]) ly[i]+=d; } } } poi ans=(poi) {0,0}; for (int i=1; i<=n; i++) ans.x+=a[f[i]][i], ans.y+=b[f[i]][i]; return ans; } int slove(poi l,poi r) { for(int i=1; i<=n; i++) for (int j=1; j<=n; j++) g[i][j]=a[i][j]*(r.y-l.y)+b[i][j]*(l.x-r.x); poi mid=km(); if (l==mid||r==mid) return min(l.x*l.y,r.x*r.y); else return min(slove(l,mid),slove(mid,r)); } int main() { int t; scanf("%d",&t); for (int z=1; z<=t; z++) { scanf("%d",&n); for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) scanf("%d",&a[i][j]); for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) scanf("%d",&b[i][j]); for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) g[i][j]=-a[i][j];le=km(); for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) g[i][j]=-b[i][j];ri=km(); printf("%d\n",slove(le,ri)); } }