动态规划-嵌套矩阵问题

版权声明:转载请注明出处。 https://blog.csdn.net/baidu_38304645/article/details/83718985

有向无环图上的动态规划是学习动态规划的基础。很多问题都可以转化为DAG上的最长路、最短路或路径计数问题。

嵌套矩阵问题。有n个矩形,每个矩形可以用a,b来描述,表示长和宽。矩形X(a,b)可以嵌套在矩形Y(c,d)中当且仅当a<c,b<d或者b<c,a<d(相当于旋转X90度)。例如(1,5)可以嵌套在(6,2)内,但不能嵌套在(3,4)中。你的任务是选出尽可能多的矩形排成一行,使得除最后一个外,每一个矩形都可以嵌套在下一个矩形内。如果有多解,矩阵编号的字典序应该尽量小。

矩阵之间的可嵌套关系是一个典型的二元关系,二元关系可以用图来建模。如果矩阵X可以嵌套在矩阵Y里,就从X到Y连一条有向边。这个有向图是无环的,因为一个矩阵无法直接或间接地嵌套在自己内。换句话说,它是一个DAG。这样,所要求的便是DAG上的最长路径。

如何求DAG中不固定起点的最长路径呢?仿照数字三角形的做法,设d(i)表示从结点i出发的最长路长度,应该如何写状态转移方程呢?第一步只能到它的相邻点。因此:

d(i) = max \left \{ d(j) + 1|(i,j)\in E \right \}

其中,E为边集。最终答案是所有d(i)中的最大值。根据前面的介绍,可以尝试按照递推或记忆化搜索的方式计算上式。不管怎样,都需要先把图建立出来,假设用邻接矩阵保存在矩阵G中(在编写主程序之前需测试和调试程序,以确保建图过程正确无误)。接下来编写记忆化搜索程序(调用前无需初始化d数组的所有值为0):

int G[10][10],d[100],n;
int dp(int i)
{
    /*d(i) = max{d(j)+1}
    如果矩形X可以嵌套在矩形Y中 就从X到Y连一条有向边 这个有向图是无环的
    因为矩形无法间接或直接嵌套到自己内部*/
    int &ans=d[i],j; //d[i]为从节点i出发的最长路长度
    if(ans>0) //长度不可以为0 d需要初始化为0
        return ans;
    ans=1;
    for(j=1;j<=n;j++)
        if(G[i][j])
           ans=max(ans,dp(j)+1);
    return ans;
}

这里用到了一个技巧:为表项d[i]声明一个引用ans。这样,任何对ans的读写实际上都是在对d[i]进行。当d[i]换成d[i][j][k][l][m][n]这样很长的名字时,该技巧的优势就会很明显。

原题还有一个要求:如果有多个最优解,矩形编号的字典序应最小。将所有d值计算出来以后,选择最大d[i]所对应的i。如果有多个i,选择最小的i,这样才能保证字典序最小。接下来可以选择d(i)=d(j) + 1且 (i,j)\in E的任何一个j。为了让方案的字典序最小,应选择其中最小的j。

void print_ans(int i)
{
    //打印字典序最小的解
   printf("%d ",i);
   for(int j=1;j<=n;j++)
        if(G[i][j]&&d[i]==d[j]+1)
        {
            print_ans(j);//找到一个满足d[i]==d[j]+1 的节点j后就应该立刻递归
            break;       //打印从j开始的路径,在递归结束后退出循环
        }
}

注意,当找到一个满足 d[i]==d[j]+1的结点j后就应立刻递归打印从j开始的路径,并在递归返回后退出循环。如果要打印所有方案,只把break语句删除是不够的(打印完一条路径后,接着打印的是另一条路径的最后一个结点,是不正确的)。正确的方法是记录路径上的所有点,在递归结束时才一次性输出整条路径。

有趣的是,如果把状态定义成 “d(i)表示结点i为终点的最长路径长度”,也能顺利求出最优值,却难以打印出字典序最小的方案(当前结点j,选择d[j]对应的i,如果有多个i,选择最小的i。问题是此时最小不一定是全局最小。全局有却可能最大,比如第一个结点是最大的)

猜你喜欢

转载自blog.csdn.net/baidu_38304645/article/details/83718985