题意:有n个人,n<=50且为偶数。原先每个人有一个配送任务,要求在平面上不重复得经过p个点(p<=8),而且总路程最短。现在因公司经营不善裁掉后一半的人,前面每个人接管一个后半部分的人的任务。问之裁之前和裁之后的最短总路程。
对于前一个问题,这是一个典型的旅行商问题。使用经典的状压dp可在O(p^2 * 2^p)求解出最短的总路程:设dp[mask][j]为走完mask位上为1位置,且最后一步为j点的最短路线,转移时枚举下一个点即可。
对于后一个问题,我们需要求出前半部分和后半部分两两组合的最短路程,然后取一个最小的二分图带权匹配即可。其中复杂度的瓶颈在于前面枚举两两组合的tsp,需要卡一卡常,dp时应尽量避免无效状态。
时间复杂度O(n^2 * p^2 * 2^p)。
其中计算tsp的过程也可以采用一些非完美算法来降低时间复杂度,但没有必要。
/*
Author: fffasttime
Date: 2019/07/23 21:20
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define inc(i,n) for (int i=0;i<n;i++)
const int N=100010;
typedef double db;
struct vec{
int x,y;
};
db dis(vec &a, vec &b){
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
db di[16][16], dp[1<<16][16];
db tsp(int n){ //tsp circle
if (n==1) return 0;
inc(i,1<<(n-1)) inc(j,n-1) dp[i][j]=1e10;
inc(i,n-1) dp[1<<i][i]=di[i][n-1]; //force set start point at n-1
inc(i,1<<n-1){
inc(j,n-1) {
if ((i>>j&1)==0)
inc(k,n-1) if (i>>k&1)
dp[i|1<<j][j]=min(dp[i|1<<j][j],dp[i][k]+di[j][k]);
}
}
db ans=1e10;
inc(i,n-1) ans=min(ans, dp[(1<<n-1)-1][i]+di[i][n-1]);
return ans;
}
vector<vec> p[60];
db d[30][30],cx[30],cy[30],cc[30];
int to[30], ux[30], uy[30];
int m;
bool find(int u){
ux[u]=1;
for (int i=1;i<=m;i++){
if (uy[i]) continue;
if (cx[u]+cy[i]==d[u][i]){
uy[i]=1;
if (!to[i]||find(to[i])) {
to[i]=u;
return 1;
}
}
else cc[i]=min(cc[i],cx[u]+cy[i]-d[u][i]);
}
return 0;
}
db km(){
for (int i=1;i<=m;i++) for (int j=1;j<=m;j++)
cx[i]=max(cx[i],d[i][j]);
for (int i=1;i<=m;i++){
memset(ux,0,sizeof ux); memset(uy,0,sizeof uy);
inc(i,30) cc[i]=1e10;
if (find(i)) continue;
db ms; int u;
while (1){
ms=1e10;
for (int j=1;j<=m;j++) if (!uy[j]) ms=min(ms,cc[j]);
for (int j=1;j<=m;j++) if (ux[j]) cx[j]-=ms;
for (int j=1;j<=m;j++) if (uy[j]) cy[j]+=ms;
else cc[j]-=ms,cc[j]==0?u=j:0;
if (!to[u]) break;
uy[u]=ux[to[u]]=1;
u=to[u];
for (int j=1;j<=m;j++) cc[j]=min(cc[j],cx[u]+cy[j]-d[u][j]);
}
memset(ux,0,sizeof ux); memset(uy,0,sizeof uy);
find(i);
}
db ans=0;
for (int i=1;i<=m;i++) ans+=d[to[i]][i];
return ans;
}
int main(){
int n,_,x,y; cin>>n;
db dis0=0;
inc(i,n){
cin>>_;
inc(j,_) cin>>x>>y,p[i].push_back({x,y});
inc(j,_) inc(k,_) di[j][k]=dis(p[i][j],p[i][k]);
dis0+=tsp(_); //cout<<tsp(_)<<'\n';
}
inc(i,n/2) inc(j,n/2){
vector<vec> tp(p[i]);
tp.insert(tp.end(),p[j+n/2].begin(),p[j+n/2].end());
inc(k,tp.size()) inc(l,tp.size()) di[k][l]=dis(tp[k],tp[l]);
db ret=tsp(tp.size());
d[i+1][j+1]=1e6-ret;
}
m=n/2;
db mat=1e6*m-km();
printf("%.6lf %.6lf\n",dis0,mat);
return 0;
}