[动态规划] NYOJ16 矩形嵌套问题 (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)中。你的任务是选出尽可能多的矩形排成一行,使得除最后一个外,每一个矩形都可以嵌套在下一个矩形内。

思路

1.将问题抽象为适合DP的方向:
矩形之间的“可嵌套”是一种典型的有向二元关系,并且由于矩形不能自己嵌套,所以可以根据矩形的嵌套关系建出一个DAG,所求就变成了求DAG中不固定起点的最长路


2.状态转移方程:
状态定义:d(i)表示以节点i为起点的最长路径长度

d ( i ) = m a x { d ( j ) + 1 | ( i , j ) E }

其中E为边集。
于是就很容易构建出,记忆搜索的直接递归代码:

int dp(int i){
    int& ans = d[i];
    if (ans > 0) return ans;
    ans = 1;
    _for(j,0,n)
        if(G[i][j])
            ans = max(ans, dp(j)+1);
    return ans; 
}



3.最后的输出问题:
NYOJ16原题只要求输出最优解的矩形个数ans即可,但书中还讲了
(1).输出字典序最小的最优解。(矩形排列)
(2).输出所有最优解,并按字典序排序。
方法仅仅是预先保留最优解的矩形个数,随后在d中找,找到以后按字典序往下找即可,详见代码。

代码

1.NYOJ16

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;

const int maxn = 1000+10;
struct juxing{
    int a,b;
}A[maxn];
int n, G[maxn][maxn], d[maxn];

int dp(int i){
    int& ans = d[i];    // 技巧
    if (ans > 0) return ans;
    ans = 1;
    _for(j,0,n)
        if(G[i][j])
            ans = max(ans, dp(j)+1);
    return ans; 
}

int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        _for(i,0,n) scanf("%d%d",&A[i].a,&A[i].b);
        memset(G,0,sizeof(G));

        _for(i,0,n)
            _for(j,0,n)
                if (i != j)
                    if ((A[i].a < A[j].a && A[i].b < A[j].b) || (A[i].a < A[j].b && A[i].b < A[j].a))
                        G[i][j] = 1;

        memset(d,0,sizeof(d));
        int ans = 1;
        _for(i,0,n) ans = max(ans, dp(i));
        printf("%d\n",ans);
    }

    return 0;
} 

2.输出字典序最小的最优解。

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;

const int maxn = 1000+10;
struct juxing{
    int a,b;
}A[maxn];
int n, G[maxn][maxn], d[maxn];

int dp(int i){
    int& ans = d[i];    // 技巧
    if (ans > 0) return ans;
    ans = 1;
    _for(j,0,n)
        if(G[i][j])
            ans = max(ans, dp(j)+1);
    return ans; 
}

void print_ans(int i){
    printf("%d ",i+1);
    _for(j,0,n) if (G[i][j] && d[i] == d[j]+1){
        print_ans(j);
        break;
    }
}

int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        _for(i,0,n) scanf("%d%d",&A[i].a,&A[i].b);
        memset(G,0,sizeof(G));

        _for(i,0,n)
            _for(j,0,n)
                if (i != j)
                    if ((A[i].a < A[j].a && A[i].b < A[j].b) || (A[i].a < A[j].b && A[i].b < A[j].a))
                        G[i][j] = 1;

        memset(d,0,sizeof(d));
        int ans = 1;
        _for(i,0,n) ans = max(ans, dp(i));
        _for(i,0,n)
            if (ans == d[i]){
                print_ans(i);
                break;
            }
        printf("\n");
    }

    return 0;
} 

3.按字典序顺序,输出所有最优解

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <algorithm>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;

const int maxn = 1000+10;
struct juxing{
    int a,b;
}A[maxn];
int n, G[maxn][maxn], d[maxn], ans;
vector<int> ansvec;

int dp(int i){
    int& ans = d[i];    // 技巧
    if (ans > 0) return ans;
    ans = 1;
    _for(j,0,n)
        if(G[i][j])
            ans = max(ans, dp(j)+1);
    return ans; 
}

void print_ans(int i, int dep){
    ansvec.push_back(i);
    if (dep == ans-1){
        for (vector<int>::iterator iter = ansvec.begin(); iter != ansvec.end(); ++iter)
            printf("%d ",*iter+1);
        printf("\n");
    }
    _for(j,0,n) if (G[i][j] && d[i] == d[j]+1){
        print_ans(j,dep+1);
    }
    ansvec.pop_back();
}

int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        _for(i,0,n) scanf("%d%d",&A[i].a,&A[i].b);
        memset(G,0,sizeof(G));

        _for(i,0,n)
            _for(j,0,n)
                if (i != j)
                    if ((A[i].a < A[j].a && A[i].b < A[j].b) || (A[i].a < A[j].b && A[i].b < A[j].a))
                        G[i][j] = 1;

        memset(d,0,sizeof(d));
        ans = 1;
        _for(i,0,n) ans = max(ans, dp(i));
        ansvec.clear();
        _for(i,0,n)
            if (ans == d[i]){
                print_ans(i,0);
            }
    }

    return 0;
} 

思考

LRJ最后留了个问题,如果把状态定义成“d(i)表示以节点i为终点的最长路径长度”,也可以得出最优值,但很难打印出字典序最小的方案。
此刻暂时无法理解,感觉仅仅是状态的定义顺应解的输出,但也说不清楚。留给以后的自己看。

猜你喜欢

转载自blog.csdn.net/icecab/article/details/80727614