嵌套矩形(算法竞赛入门经典第九章)

题目很简单,大约是这个意思:

有n个矩形,每个矩形可以用两个整数a,b描述,表示它的长和宽。你的任务就是选出尽量多的矩形排成一行,使得除最后一个以外,每个矩形都可以嵌套在下一个矩形内。

算法竞赛这本书提供了这样两段代码:

int dp(int i) 
{
	int& ans = d[i];
	if(ans > 0) return ans;
	ans = 1;
	for(int j = 1; j <= n; j++)
		if(G[i][j]) ans >?= dp(j) + 1;
	return ans;
}

void printf_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);
		break;
	}
}

由于我个人的代码是C语言的,所以与其不同。对于int& ans = d[i],让我纠结了很久,这本书上是这解释其目的:“这里用到了一个技巧:为表明d[i]申明一个引用ans。这样,任何对ans的读写实际上都是在对d[i]进行。”但我一直编译不通过,最后只得把int& ans = d[i]这句话去掉了。也许人品不好吧,技巧都用不了。。。对于ans >?= dp(j) + 1这样语句还是要翻译一下的,意思也就是ans等于ans与dp(j) + 1之间的较大的那一个。好吧,最后还有第二段代码中输出语句,我认为应放在最后,这样才保证是按由小到大输出矩形序号的。这本书上的分析还是比较抽象的,下面我来让它具象具象吧。

首先用一个结构体rect保存矩形(rect.a、rect.b保存其长、宽,不一定是一一对应的),申明一个二维数组G[X][X](G[i][j]表示第j个矩形是否能被第i个矩形接纳),一个一维数组d[X](d[i]记录的就是第i个矩形能娶到的矩形个数,初始为0)。再按序从1-n,枚举矩形i,再看1-n里面每一个矩形j是否愿意嫁给这个矩形,若愿意G[i]j[j] = 1,否则G[i]j[j] = 0。这样所有矩形i(1-n)都有清楚1-n这里面矩形是否愿嫁给自己(此处复杂度为n^2);准备工作做好之后就开始传说中的dp了。。。dp(i)其实就是简单的从1-n浏览一遍,看是否有个j愿意嫁给他,即G[i][j]是否为1,但i不傻,因为i娶了j之后,以前嫁给j的矩形就会变成i的小矩形,所以i会娶已经拥有矩形最多的j。所以i首先判断自己现在的状况和娶了j之后的状况d[i] = max(d[i], dp(j) + 1) 在1-n循环之后,d[i]就是最大的了。此处还用到记忆化搜索,就是在dp之初,判断一下d[i]是否为0,因为默认是0的,若不是,就直接返回d[i],避免了重复计算。

代码如下:

#include<stdio.h>
#include<string.h>
#define MAXN 1000

int G[MAXN][MAXN], d[MAXN], n;

int max(int x, int y){
    return x > y ? x : y;
}

struct rectangle{               //记录矩形
    int a, b;
}rect[MAXN];

int dp(int i){
    int j;
    if(d[i] != 0)               //记忆化搜索,如果已经计算过d[i]就直接放回d[i]
        return d[i];
    d[i]++;                     //如果d[i] = 0 就自增一下,因为会计算矩形自身的
    for(j = 1; j <= n; j++) if(G[i][j])         //如果i能包含j
        d[i] = max(d[i], dp(j) + 1);            //找d[i]最大值
    return d[i];
}

void printf_ans(int i){
    int j;
    for(j = 1; j <= n; j++) if(G[i][j] && d[i] == d[j] + 1){
        printf_ans(j);
        break;
    }
    printf("%d ", i);                          //因为d[i]中的i是最大的矩形的序号,所以应该最后输出,所以输出语句至于最后
}

int main(){
    int i, t, j, mmax;
    scanf("%d", &t);
    while(t--){
        mmax = 0;
        scanf("%d", &n);
        for(i = 1; i <= n; i++)
            scanf("%d%d", &rect[i].a, &rect[i].b);
        memset(G, 0, sizeof(G));
        memset(d, 0, sizeof(d));
        for(i = 1; i <= n; i++){
            for(j = 1; j <= n; j++)
                if((rect[i].a > rect[j].a && rect[i].b > rect[j].b) || (rect[i].a > rect[j].b && rect[i].b > rect[j].a)){//考虑到矩形长宽的变换
                    G[i][j] = 1;
                    //printf("G[%d][%d] = %d\n", i, j, G[i][j]);        //调试时看矩形包含关系是否正确
            }
        }
        for(i = 1; i <= n; i++){    //找出最大的矩形和其包含矩形的数目
            if(mmax < dp(i)){
                j = i;              //借用j记录最大d[i]的序号
                mmax = dp(i);
            }
        }
       printf("%d\n", mmax);
       printf_ans(j);
       printf("\n");
    }
    return 0;
}

以上“具象”的比喻文字绝无恶意,纯属简化题意。。。请勿误解。。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值