学习动态规划DP(三)最优匹配问题

前言:《算法竞赛入门经典》中将最优匹配问题归在复杂状态的动态规划下,第一次看的时候完全不懂…,隔几天后再看顿时豁然开朗,快点写下笔记和总结。在此问题之前还有树上的动态规划,以后再总结一下。

问题描述:空间里有n个点P0,P1,…,Pn-1,你的任务是把它们配成n/2对(n是偶数),使得每个点恰好在一个点对中。所有点对中两点的距离之和应尽量小。其中 n≤20。

要匹配的点最多才20个,所以这里可以把所有点看成一个点集,并且可以用二进制表示方法来表示这个集合。(学的时候感觉,太强了,太好用了。但需要先明白位运算)。
如:01101 表示第 0,2,3个元素存在于集合中,相当于全集的子集表示。由于二进制为从0开始,所以上述的点编号也从0开始。
用二进制表示的好处:便于寻找那些点在集合中,便于划分子集

思路:要求的是所有点的最优匹配,我们把大问题划分为小问题,再根据小问题所求 的解来求解大问题;
先求解任意一个只含有两个点的子集,求这些子集的最优解(即求解 点 i 到
全集S中其他点的距离的最小值);
再求解任意一个只含有四个点的子集,因为只含有两个点的子集已经计算过。
所以我们在原来计算过的基础上,在S中寻找其他的两个点,计算它们的距离。
再将这四个点并成一个集合,同样地要取最小解。
(即若原来0,1两点已经匹配好,那我们要求的是,所有包含0和1且含有四个点
的集合的最优解)
这样依次类推,直到求得全集的最优解 。
(我们每次求得的解都是,含有某些配对的集合的最优解)

定义状态:d[s] 表示 点集s的最优匹配,则状态方程表示为:
d[s] = min( d[s] , dist(i,j)+d[s^(i<< i )^(1<< j )];
其中s^(1<< i)^(1<< j) 表示 集合s除去 i和j 的子集;
相当于一种构造集合的方式:通过往集合中添加一对匹配的点构造集合(一开始为空集)。

下面是通过紫书学习的代码,注释的都是我学习时一点点弄懂的;原书上代码有一点小错误,有读者在看原书的话注意一下。

#include
#include
using namespace std;

const int N = 20;
const int INF = 1e5;

struct point
{
    int x,y,z;
} p[N+1];

double d[1<// DP数组 

//计算两点距离, 点为空间上的点 
double dist(int i,int j)
{
    return sqrt(pow(p[i].x-p[j].x,2)+pow(p[i].y-p[j].y,2)+pow(p[i].z-p[j].z,2));
}

double dp(int n)
{
    d[0]=0;//集合中没有元素,初始化为0

    //s表示集合的所有不同状态,包含了S所有的子集,因为二进制数 1,10,11,100,101,...,代表了全集S的不同子集 
    //这样遍历集合的所有状态,可以发现总是先从某个子集所含元素最少的子集开始遍历
    //如:3->11,表示第0和第1个点的集合,而7->111表示第0,第1和第2个点的集合,总是遍历过前者在遍历后者 
    for(int s=1; s<(1<//因为要求最小,所以先赋值为较大值 
        int i,j;
        //寻找子集的一个点,用该点跟其他点匹配,这里得到的 i 是子集s中最大的元素
        //这样寻找点的好处是确保i总是在S中 
        for(i=n-1; i>=0; --i)
            if(s&(1<//判断元素是否在集合中 
                break;

        //对上一段 for 学习时我有一个疑问:为什么对每一子集都只是取其中一个点去配对其他的点?
        //举例:若当前子集为s={0,1,2,3,4,5}, 每次我们只取5去和剩下的点匹配,那(1,2),或者(2,3)呢?
        //其实,上面已经说过,当我们求(0,5)时,s1 = s-0-5={1,2,3,4},s1是已经计算好了的;
        //即我们已经尝试过了(1,2),(1,3),(1,4),(2,3),(2,4),(3,4),然后取最优解赋值给了d[4]
        //所以实际上我们确实有考虑了所有不同的组合          


        //因为取得子集中最大的元素,所以就从 i-1 找该子集的其他点了 
        for(j=i-1; j>=0; --j)
            if(s&(1<//判断元素是否在集合中 
            // s^(1<
                d[s] = min(d[s],dist(i,j)+d[s^(1<1<return d[(1<1]; //(1<
}
int main()
{
    int n;
    cin>>n;
    for(int i=0; icin>>p[i].x>>p[i].y>>p[i].z;
    cout<

下面是递归代码,道理同上,就不一一注释了。

#include
#include
using namespace std;
const int N = 20;
const int INF = 1e5;

struct point
{
    int x,y,z;
} p[N+1];

double d[1<// DP数组 
int n;
//计算两点距离, 点为空间上的点 
double dist(int i,int j)
{
    return sqrt(pow(p[i].x-p[j].x,2)+pow(p[i].y-p[j].y,2)+pow(p[i].z-p[j].z,2));
}
double dp(int s)
{
    if(d[s]!=INF) return d[s]; 
    int i,j;
    for(i=n-1;i>=0;--i)
        if(s&(1<break;
    for(j=i-1;j>=0;--j)
        if(s&(1<1<1<return d[s];
}
int main()
{
    cin>>n;
    for(int i=0; icin>>p[i].x>>p[i].y>>p[i].z;
    for(int s=1;s<(1<0]=0;
    //从全集开始算 
    cout<1<1)<

你可能感兴趣的:(学习动态规划DP(三)最优匹配问题)