UVa 103 Stacking Boxes

题目描述

我们有一个 nnn 维“盒子”,其尺寸由一个 nnn 元组表示。两个盒子 DDDEEE 之间存在“嵌套”关系,如果存在 DDD 尺寸的一个排列,使得排列后的每个维度都严格小于 EEE 的对应维度。给定 kkknnn 维盒子,要求找出最长的嵌套序列 b1,b2,…,bmb_1, b_2, \dots, b_mb1,b2,,bm,使得 bib_ibi 能嵌套在 bi+1b_{i+1}bi+1 中。

输入

  • 包含多个测试用例。
  • 每个用例第一行:盒子数量 kkk 和维度 nnn
  • 接下来 kkk 行:每个盒子的 nnn 个尺寸。

输出

  • 第一行:最长嵌套序列的长度。
  • 第二行:构成该序列的盒子编号(按输入顺序编号,从 111 开始),从最内层(最小)盒子开始输出

约束

  • 1≤n≤101 \leq n \leq 101n10
  • 1≤k≤301 \leq k \leq 301k30

题目分析与解题思路

1. 问题转化

判断一个盒子 DDD 能否嵌套在另一个盒子 EEE 中,关键在于是否存在一个排列,使得 DDD 的每个尺寸都小于 EEE 的对应尺寸。

关键观察
如果我们将每个盒子的尺寸先按升序排序,那么 DDD 能嵌套在 EEE 中的条件就简化为了:排序后的 DDD 的每个维度都严格小于排序后的 EEE 的对应维度。
这是因为:

  • 如果存在某种排列使得 DDD 能嵌套在 EEE 中,那么将 DDDEEE 都按升序排序后,由于 EEE 的排序只是其自身维度的重排,不会影响比较;而 DDD 排序后,其最小对最小、次小对次小……的比较必然满足条件。
  • 反之,如果排序后的 DDD 每个维度都小于排序后的 EEE 的对应维度,那么显然存在一种排列(即排序后的顺序)满足嵌套条件。

因此,我们可以先对每个盒子的尺寸进行升序排序,然后直接比较排序后的向量即可判断嵌套关系。

2. 建立有向无环图(DAG\texttt{DAG}DAG

将每个盒子视为一个结点。如果盒子 AAA(排序后)能嵌套在盒子 BBB(排序后)中,则添加一条从 AAABBB 的有向边。
这样我们就得到了一个有向无环图(DAG\texttt{DAG}DAG),因为嵌套关系是传递的且不会出现循环(否则会出现盒子能嵌套在自己内部的情况,与尺寸严格小于矛盾)。

3. 求最长路径

问题转化为:在 DAG\texttt{DAG}DAG 中求最长路径(即最长嵌套序列)。
由于 k≤30k \leq 30k30,我们可以使用动态规划(DP\texttt{DP}DP)求解:

设:

  • dp[i]dp[i]dp[i]:以盒子 iii 为起点(即最内层盒子)的最长路径长度。
  • next[i]next[i]next[i]:在最长路径中,盒子 iii 的下一个盒子编号(用于重建路径)。

状态转移方程:
dp[i]=1+max⁡j:boxi nests in boxjdp[j] dp[i] = 1 + \max_{j: \text{box}_i \text{ nests in box}_j} dp[j] dp[i]=1+j:boxi nests in boxjmaxdp[j]
其中,如果不存在可嵌套的 jjj,则 dp[i]=1dp[i] = 1dp[i]=1(只包含自己)。

我们需要对所有盒子按某种顺序进行 DP,确保在计算 dp[i]dp[i]dp[i] 时,所有可能的后继 jjjdp[j]dp[j]dp[j] 已经计算好。
一个简单的方法是将所有盒子按尺寸和(或按排序后的字典序)排序,然后从“最大”的盒子开始计算(因为大盒子不会被小盒子嵌套,所以它的 dpdpdp 值只依赖自己)。
另一种更通用的方法是记忆化搜索(递归+缓存),这样无需显式拓扑排序。

4. 路径重建与输出顺序

题目要求输出时从最内层盒子开始,即路径的起点。
因此,我们最终需要找到 dpdpdp 值最大的盒子作为起点,然后沿着 nextnextnext 指针输出,直到无后继为止。

注意:盒子编号需按输入顺序给出,因此我们在排序前需要记录原始编号。

5. 复杂度分析

  • 排序每个盒子的维度:O(k⋅nlog⁡n)O(k \cdot n \log n)O(knlogn)
  • 建立嵌套关系:O(k2⋅n)O(k^2 \cdot n)O(k2n)(共 k2k^2k2 对盒子,每对比较 nnn 维)
  • DP\texttt{DP}DP过程:O(k2)O(k^2)O(k2)
  • 总复杂度在给定约束下完全可行。

代码实现

// Stacking Boxes
// UVa ID: 103
// Verdict: Accepted
// Submission Date: 2011-10-17
// UVa Run Time: 0.012s
//
// 版权所有(C)2011,邱秋。metaphysis # yeah dot net
//
// [解题方法]
// 该问题可以归结为求最长上升子序列的问题。

#include <bits/stdc++.h>

using namespace std;

#define MAXD 10
#define MAXN 30
#define END (-1)

struct box
{
public:
    int dimensionality[MAXD];
    int dimensions;
    int index;

    bool operator<(const box &other) const
    {
        for (int i = 0; i < dimensions; i++)
            if (dimensionality[i] != other.dimensionality[i])
                return dimensionality[i] < other.dimensionality[i];
        return false;
    }

    bool nests(const box &other) const
    {
        for (int i = 0; i < dimensions; i++)
            if (dimensionality[i] >= other.dimensionality[i])
                return false;       
        return true;
    }
};

box boxes[MAXN];
int longest[MAXN];
int ancestor[MAXN];
int nBoxes, nDimensions;

void sequences(int current)
{
    if (ancestor[current] != END)
        sequences(ancestor[current]);
    cout << (ancestor[current] == END ? "" : " ") << (boxes[current].index + 1);
}

void lis(void)
{
    for (int i = 0; i < nBoxes; i++)
        for (int j = 0; j < i; j++)
            if (boxes[j].nests(boxes[i]) && (longest[j] + 1) > longest[i])
            {
                longest[i] = longest[j] + 1;
                ancestor[i] = j;
            }

    int maximum = 0, marker;
    for (int i = 0; i < nBoxes; i++)
        if (maximum < longest[i])
        {
            maximum = longest[i];
            marker = i;
        }

    cout << maximum << endl;
    sequences(marker);
    cout << endl;
}

int main(int ac, char *av[])
{
    while (cin >> nBoxes >> nDimensions)
    {
        for (int i = 0; i < nBoxes; i++)
        {
            longest[i] = 1;
            ancestor[i] = END;

            boxes[i].dimensions = nDimensions;
            boxes[i].index = i;

            for (int j = 0; j < nDimensions; j++)
                cin >> boxes[i].dimensionality[j];
            sort(boxes[i].dimensionality, boxes[i].dimensionality + nDimensions);
        }

        sort(boxes, boxes + nBoxes);

        lis();
    }

    return 0;
}

算法步骤总结

  1. 读入数据:对每个盒子的尺寸进行升序排序,并记录原始编号。
  2. 整体排序:将所有盒子按排序后的尺寸字典序排序(方便后续 DP\texttt{DP}DP)。
  3. 动态规划求最长嵌套序列
    • longest[i] 表示以盒子 iii 结尾的最长序列长度。
    • ancestor[i] 记录在最长序列中盒子 iii 的前驱。
    • 遍历所有盒子对 (j,i)(j, i)(j,i)j<ij < ij<i),如果 boxes[j] 能嵌套在 boxes[i] 中,则更新 longest[i]ancestor[i]
  4. 输出结果:找到 longest 最大的位置,递归输出路径。

示例

以题目样例输入为例:

5 2
3 7
8 10
5 2
9 11
21 18

处理过程:

  1. 每个盒子排序后得到(括号内为原始编号):
    • (1) 3 7
    • (3) 2 5
    • (2) 8 10
    • (4) 9 11
    • (5) 18 21
  2. 按字典序排序后顺序为:(3), (1), (2), (4), (5)
  3. 嵌套关系:
    • (3) → (1) → (2) → (4) → (5)
    • 最长序列长度为 555,路径为 3 1 2 4 5

输出:

5
3 1 2 4 5

注意事项

  • 盒子编号从 111 开始,代码中存储时从 000 开始,输出时需 +1+1+1
  • 可能存在多个最长序列,输出任意一个即可。
  • 输入可能包含多个测试用例,需持续处理直到文件结束。

通过以上分析,我们将嵌套问题转化为 DAG\texttt{DAG}DAG 上的最长路径问题,并利用动态规划高效求解。代码实现简洁,运行效率高,能够处理最大规模的输入数据。

### Stacking in Machine Learning Ensemble Methods Stacking, also known as stacked generalization, is a meta-algorithm that combines multiple classification or regression models via a meta-classifier/meta-regressor. Unlike bagging and boosting which focus on reducing variance and bias respectively, stacking aims at increasing the predictive power by leveraging predictions from different algorithms. In stacking, the training set is split into two parts. The first part trains each base learner while the second part forms what can be considered an out-of-sample dataset used to train the combiner model. This ensures that when generating features for the level-one model, there's no data leakage between training sets of individual learners and the overall stacker[^1]. The process involves using outputs from various classifiers/regressors as inputs/features for another classifier/regressor called the blender or meta-model. By doing this, stacking allows capturing patterns missed individually by constituent models leading potentially better performance than any single algorithm could achieve alone. ```python from sklearn.datasets import make_regression from sklearn.model_selection import train_test_split from mlens.ensemble import SuperLearner from sklearn.linear_model import LinearRegression from sklearn.svm import SVR import numpy as np X, y = make_regression(n_samples=1000, n_features=20, noise=0.5) X_train, X_test, y_train, y_test = train_test_split(X, y) layer_1 = SuperLearner() layer_1.add([LinearRegression(), SVR()]) layer_1.add_meta(LinearRegression()) layer_1.fit(X_train, y_train) predictions = layer_1.predict(X_test) print(f"Predictions shape: {np.shape(predictions)}") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值