一看TM就不会做
赶紧学了一发最小乘积生成树和最小乘积最大匹配
大概就是把每个完备匹配后的结果看成一个点(sigma(a),sigma(b)),发现答案都在下凸壳上,然后用分治递归找下凸壳就好了。
首先找到下凸壳两端的点(横坐标最小和纵坐标最小的两个点),然后连线,找到离线最远的点(叉积推公式,KM/费用流找匹配),然后分治,直到最远的点就是两点之一了,也就是两点是下凸壳上相邻的两点。
(不会写KM了,写了个费用流水过去了,感觉后面两个点妥妥的T了)
#include<iostream> #include<cstdio> #include<cstring> #include<queue> using namespace std; const int N=70+5; const int inf=1e8; struct Edge{int from,to,next,v,c;}e[N*N*3]; int head[N<<1],cnt; void init(){memset(head,0,sizeof(head));cnt=1;} void ins(int u,int v,int w,int c){ e[++cnt]=(Edge){u,v,head[u],w,c};head[u]=cnt; } void insert(int u,int v,int w,int c){ins(u,v,w,c);ins(v,u,0,-c);} int d[N<<1],from[N<<1]; bool inq[N<<1]; bool spfa(int s,int t,int &flow,int &cost){ memset(d,0x3f,sizeof(d));d[s]=0;queue<int>q;q.push(s); while(!q.empty()){ int u=q.front();q.pop();inq[u]=false; for(int i=head[u];i;i=e[i].next) if(e[i].v&&d[e[i].to]>d[u]+e[i].c){ d[e[i].to]=d[u]+e[i].c; from[e[i].to]=i; if(!inq[e[i].to]){ inq[e[i].to]=true; q.push(e[i].to); } } } if(d[t]>=inf)return false; int x=inf; for(int i=from[t];i;i=from[e[i].from])x=min(x,e[i].v); flow+=x;cost+=d[t]*x; for(int i=from[t];i;i=from[e[i].from]) e[i].v-=x,e[i^1].v+=x; return true; } int mcf(int s,int t){int flow=0,cost=0;while(spfa(s,t,flow,cost));return flow;} struct point{ int x,y; bool operator == (const point &rhs)const{ return x==rhs.x&&y==rhs.y; } bool operator < (const point &rhs)const{ if(x!=rhs.x)return x<rhs.x; return y<rhs.y; } }; int a[N][N],b[N][N]; int n; point solve(int ka,int kb){ init(); int S=0,T=2*n+1; for(int i=1;i<=n;i++){ insert(S,i,1,0);insert(i+n,T,1,0); for(int j=1;j<=n;j++) insert(i,j+n,1,a[i][j]*ka+b[i][j]*kb); } mcf(S,T); int suma=0,sumb=0; for(int i=1;i<=n;i++) for(int j=head[i];j;j=e[j].next) if(!e[j].v&&e[j].to!=S) suma+=a[i][e[j].to-n],sumb+=b[i][e[j].to-n]; return (point){suma,sumb}; } int solve(point a,point b){ init(); point t=solve(a.y-b.y,b.x-a.x); if(t==a||t==b)return min(a.x*a.y,b.x*b.y); else return min(solve(a,t),solve(t,b)); } int main(){ //freopen("a.in","r",stdin); int T;scanf("%d",&T); while(T--){ 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]); point p1=solve(1,0),p2=solve(0,1); printf("%d\n",solve(p1,p2)); } return 0; }
不会写KM简直不像话啊,果断写了一发KM压压惊,速度是费用流的十倍QAQ,BTW:宏定义真好用
#include<iostream> #include<cstdio> #include<cstring> using namespace std; #define rep(i,l,r) for(int i=l;i<=r;i++) #define mmt(x,y) memset(x,y,sizeof(x)) const int N=70+5; const int inf=1e8; int w[N][N],slack[N],n,linked[N],lx[N],ly[N]; bool visx[N],visy[N]; bool match(int u){ visx[u]=true; rep(v,1,n){ if(visy[v])continue; int d=lx[u]+ly[v]-w[u][v]; if(!d){ visy[v]=true; if(linked[v]==-1||match(linked[v])){ linked[v]=u; return true; } }else slack[v]=min(slack[v],d); } return false; } struct point{ int x,y; bool operator == (const point &rhs)const{ return x==rhs.x&&y==rhs.y; } }; int a[N][N],b[N][N]; point KM(int ka,int kb){ rep(i,1,n)rep(j,1,n)w[i][j]=a[i][j]*ka+b[i][j]*kb; mmt(linked,-1);mmt(lx,-0x3f);mmt(ly,0); rep(i,1,n)rep(j,1,n)lx[i]=max(lx[i],w[i][j]); rep(i,1,n){ mmt(slack,0x3f); while(true){ mmt(visx,0);mmt(visy,0); if(match(i))break; int d=inf; rep(j,1,n)if(!visy[j])d=min(d,slack[j]); rep(j,1,n){ if(visx[j])lx[j]-=d; if(visy[j])ly[j]+=d; else slack[j]-=d; } } } int suma=0,sumb=0; rep(i,1,n)if(linked[i]!=-1)suma+=a[linked[i]][i],sumb+=b[linked[i]][i]; return (point){suma,sumb}; } int solve(point a,point b){ point t=KM(b.y-a.y,a.x-b.x); if(a==t||b==t)return min(a.x*a.y,b.x*b.y); else return min(solve(a,t),solve(t,b)); } int main(){ //freopen("a.in","r",stdin); int T;scanf("%d",&T); while(T--){ 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]); point p1=KM(-1,0),p2=KM(0,-1); printf("%d\n",solve(p1,p2)); } return 0; }