最优匹配问题

题意求集合大小为偶数的两两匹配的最小权值和。(比如说定义为两点间的距离)

设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;
}

 

posted @ 2017-10-25 22:05  yohanlong  阅读(847)  评论(0编辑  收藏  举报