题意求集合大小为偶数的两两匹配的最小权值和。(比如说定义为两点间的距离)
设di,S 为前i个数,已选的点的集合是S的最小权值和。
容易想到的状态转移方程为
di,S = min{di,S , di-1,s-{i}-{j} + p[i][j]} |i,j∈{S}
但是这样每一次转移都要枚举i,j,再加上S贡献的2n的复杂度,总复杂度为O(n2*2n),无法接受。
但是我们发现,我们的i其实可以包含在S里面,只需要保证i是S中的最大值或者最小值即可,这个可以方便的维护。
即
dS = min{dS , ds-{i}-{j} + p[i][j]} |i=max{S}, j∈{S}
紫书上说,平均获得最大或者最小值的判断次数为2,这个怎么证明呢?
对于最后一个1在第n位的,一共有2n状态要算,每个状态算一次,因为遍历到n的时候就可以直接break了。
对于最后一个1在第n-1位的(此时第n位一定是0),一共有2n-1状态要算,每个状态算两次。
……
所以一共算的次数是2n*1+2n-1*2+2n-2*3+……+21*n
这个用高中的数学知识求和(隔位相减法可以求得)最高次是2n+1级别的。
最后全部除以2n得到均摊复杂度是2。(其他次数小于2n+1的全部视为“无穷小量”)
得证。
#include <cstdio> #include <algorithm> #include <cmath> #define db double using namespace std; const int maxn = 105, inf = 1e9; int n; db d[1 << 20 + 5]; struct node { int x, y, z; }a[maxn]; db dis(int i, int j) { return sqrt((a[i].x - a[j].x) * (a[i].x - a[j].x) + (a[i].y - a[j].y) * (a[i].y - a[j].y)); } int main() { freopen("最优配对问题.in","r",stdin); scanf("%d", &n); for (int i = 1; i <= n; i++) { scanf("%d%d%", &a[i].x, &a[i].y); } for (int S = 0; S < (1 << n); S++) { d[S] = S == 0 ? 0 : inf; int j; for (j = n - 1; j >= 0; j--) if (S & (1 << j)) break;//如此可以保证每次选出来的j是最大的 for (int i = 0; i < j; i++) if (S & (1 << i)) d[S] = min(d[S], dis(i + 1, j + 1) + d[S ^ (1 << (i + 1)) ^ (1 << (j + 1))]); } printf("%f", d[(1 << n) - 1]); return 0; }