真_详解动态规划_DAG_嵌套矩形

参考书目:算法竞赛入门经典(第2版)刘汝佳

DAG: Directed ayclic graph // 有向无环图

刘汝佳:DAG是动态规划的基础,很多问题都可以转化成DAG上的最短、最长或路径计数问题

如NYOJ 16

嵌套矩形问题,

描述

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

输入

第一行是一个正数N(0<N<10),表示测试数据组数,
每组测试数据的第一行是一个正正数n,表示该组测试数据中含有矩形的个数(n<=1000)
随后的n行,每行有两个数a,b(0<a,b<100),表示矩形的长和宽

输出

每组测试数据都输出一个数,表示最多符合条件的矩形数目,每组输出占一行

样例输入

1
10
1 2
2 4
5 8
6 10
7 9
3 1
5 8
12 10
9 7
2 2

样例输出

5

分析:学过离散数学都知道二元关系,显然矩形嵌套关系可以看作一个二元关系,而二元关系可以用图来表示。而且,由于a<c,b<d或者b<c,a<d(没有取等号),所以一个矩形不能被自己(或与自己大小相同的矩形)所嵌套。因此该二元关系的图必是无环的而且是有向的(不存在相互嵌套情况)。即该图为DAG,如果将每一个矩形都看作图中的一个点,若一个矩形可以(被)嵌套另一个矩形,则对应两点之间有单向通路,否则不连通,则该问题就转化为了求DAG上不确定起点的最长路径问题。

解答:

1.存储矩形:用结构体存储

2.存储二元关系图:用二维数组存储,配对遍历(n*n)所有矩形,利用条件:a<c,b<d或者b<c,a<d进行判断是否连通,并进行标记,其中的权重仅仅是标记。

3.定义函数dis(t):从t出发得到的最长路线,则所求为:最大的dis(i)。因此必须将所有的dis(i)给计算出来,进行打擂台求最大值。

4.求所有的dis(i):根据动态规划的思想:将问题规模化小,转移状态,将问题规模缩小。从第i点开始考虑,面对每一个与之相邻的点 j 都可以选择走和不走。

4-1.走:由于从第i点只能走到与之相邻的点j,如果将第i点移到第j点,要计算dis(i) 则需要计算dis(j), 因为dis(i) = dis(j)+1(为什么该等式成立请看:从 i 点考虑,dis(i)为从第 i点出发的最长长度,而第i点与第j点连通,所以第 i 点的最大长度dis(i)显然可以加上dis[j](再加上i与 j之间的距离一般设置为1即可)还不懂的话:将 i 点看作 j 点的先导,打个比方:要到打饭窗口必须先到食堂门口,所以要求到打饭窗口的距离,只要求到食堂门口的距离再加上食堂门口到打饭窗口的距离(将食堂门口和打饭窗口看作两个连通的点,这个距离就是我们设置的权重一般设置为1就可以对应题目所求数量))

就是要你懂_图解:

(该图为简化后的二元关系图图中的圆圈表示二元关系图中的点,有箭头的线表示有向连通,距离是无论方向的,由图很容易理解为什么会存在dis[i] = dis[j] + 1这种情况)

4-2.不走:由于状态没有改变,dis(i)的值不发生变化。

5.得出动态转移方程dis(i) = max(dis(i),dis(j)+1) , (i,j)∈E (E为边集)

数据结构

矩形:结构体数组

二元关系图:二维数组

dis( ): 一维数组

代码:

#include<stdio.h>
#include<string.h>
#define MAX 500
#define max(a,b) a>b?a:b
typedef struct Rectangle
{
    int length;
    int width;
} rectangle;

int graph[MAX][MAX];//图
rectangle rect[MAX];//矩形
int dis[MAX];//dis()
int n;

int dynamic_program(int i);
int dynamic_program(int i)
{
    if(dis[i]>0)//如果已经计算过,则直接返回
        return dis[i];
    dis[i] = 1;//用于标记,表示已经计算出结果,减少重复计算过程,同时将所有dis[]初始化为1,用于计算。
    for(int j = 0 ; j<n ; j++)
        if(graph[i][j])
            dis[i] = max(dynamic_program(j)+1,dis[i]);//递归计算dis[j]
    return dis[i];
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        memset(graph,0,sizeof(graph));
        memset(dis,0,sizeof(dis));

        scanf("%d",&n);

        for(int i = 0 ; i<n ; i++)
            scanf("%d %d",&rect[i].length,&rect[i].width);

        for(int i = 0 ; i<n ; i++)
            for(int j = 0 ; j<n ; j++)
                if(((rect[i].length<rect[j].length)&&(rect[i].width<rect[j].width))
                        ||((rect[i].length<rect[j].width)&&(rect[i].width<rect[j].length)))//大于还是小于没有关系,只要四个保持一致即可(试想将输入矩形顺序打乱不会影响结果,就可以想通了)
                    graph[i][j] = 1;

        int ans = -1;
        for(int i = 0 ; i<n ; i++)
        {
            dis[i] = dynamic_program(i);
            if(dis[i]>ans)
                ans = dis[i];
        }
        printf("%d\n",ans);
    }
    return 0;
}

附上二元关系图的二维数组方便读者验证( 连通则为1,否则为0.)

0 1 1 1 1 0 1 1 1 0
0 0 1 1 1 0 1 1 1 0
0 0 0 1 1 0 0 1 1 0
0 0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 1 0 0
0 1 1 1 1 0 1 1 1 0
0 0 0 1 1 0 0 1 1 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 1 0 0
0 0 1 1 1 0 1 1 1 0

 

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值