ACM: 动态规划 华东师范OJ 1244  …

此博客介绍了如何解决一个积木游戏的优化问题,玩家需用有限数量的积木堆成多根柱子,使得总高度最大化。通过动态规划的方法,确定了状态转移方程和决策过程,最终实现求解最大高度的算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

积木游戏
Description


SERCOI 最近设计了一种积木游戏。每个游戏者有N块编号依次为1 ,2,…,N的长方
体积木。对于每块积木,它的三条不同的边分别称为”a边”、“b边”和”c边”,如下图所示:
ACM: <wbr>动态规划 <wbr>华东师范OJ <wbr>1244 <wbr> <wbr>花了一个上午的AC的题目T.T

游戏规则如下:
1、从N块积木中选出若干块,并将它们分成M(l<=M<=N) 堆,称为第1堆,第2 堆…,第M堆。每堆至少有1块积木,并且第K堆中任意一块积木的编号要大于第K+1堆中任意一块积木的编号(2<=K<=M)。

2.对于每一堆积木,游戏者要将它们垂直摞成一根柱子,并要求满足下面两个条件:
(1)除最顶上的一块积木外,任意一块积木的上表面同且仅同另一块积木的下表面接触,并且要求下面的积木的上表面能包含上面的积木的下表面,也就是说,要求下面的积木的上表面的两对边的长度分别大于等于上面的积木的两对边的长度。

(2)对于任意两块上下表面相接触的积木,下面的积木的编号要小于上面的积木的编号。

最后,根据每人所摞成的M根柱子的高度之和来决出胜负。
请你编一程序,寻找一种摞积木的方案,使得你所摞成的M根柱子的高度之和最大。

Input


第一行有两个正整数N和M(1<=M<=N<=100),分别表
示积木总数和要求摞成的柱子数。这两个数之间用一个空格符隔开。接下来N行依次是编号
从1到N的N个积木的尺寸,每行有三个;至1000之间的整数,分别表示该积木a边,b边
和c边的长度。同一行相邻两个数之间用一个空格符隔开。

Output


输出只有一行,为一个整数,表示M根柱子的高度之和。

Sample Input



4 2
10 5 5
8 7 7
2 2 2
6 6 6

Sample Output

24


题意: 要你用n块积木对出m根柱子, 要求顺序取积木, 并且下面一块积木的表面要可以包含上面一块积木的底面.

          要你求出最大的柱子的高度和.


解题思路: 黑书上的题目. 值得反复思考.

              1. 题目有三种决策: 放弃当前块不用, 当前块另外起一根柱子, 当前块加在当前柱子上.

              2. 状态: dp[i][a][b][k]: 表示前a块积木得到了i根柱子, 目前顶面上的积木编号为b的上表面是k,

                                               以后还可以得到的最大高度和.

              3. 状态转移:  (这题采用的是向前递推) n是积木块数, m是要形成柱子数

                             决策: 放弃当前块:  dp[i][a][b][k] = dp[i][a][b][kk]  成立条件: 任何情况下都可以使用.

                                      当前块另外起一根柱子: dp[i][a][b][k] = dp[i][a][a+1][kk] 成立条件: 当i < m时.

                                      当前块加在当前柱子上: dp[i][a][b][k] = dp[i][a][a+1][kk] 成立条件: 积木a+1的面kk,可以被

                                                                                                                                           积木b的的面k接受.

              4. 一开始遇到的问题是: 不知道怎么判断决策3的成立条件怎么判定.

代码:

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
#define MAX 105

int n, m;
int a[MAX][3];  //积木的三边
int dp[MAX][MAX][MAX][3]; //dp[i][a][b][k]: 表示用前a个积木得到了i根柱子,目前定面的积木b的面是k
                          //还能获得的最大高度
inline int max(int a,int b)
{
    return a > b ? a : b;
}

inline int min(int a,int b)
{
    return a < b ? a : b;
}

inline int getH(int i,int k)
{
    return a[i][2-k]; //k面要取不在这个面上的边的长度
}

inline bool canput(int b,int k,int bb,int kk)
{
    int len1, len2, len3, len4;
    if(k == 0)
    {
        len1 = max(a[b][0],a[b][1]);
        len2 = min(a[b][0],a[b][1]);
    }
    if(k == 1)
    {
        len1 = max(a[b][0],a[b][2]);
        len2 = min(a[b][0],a[b][2]);
    }
    if(k == 2)
    {
        len1 = max(a[b][2],a[b][1]);
        len2 = min(a[b][2],a[b][1]);
    }
   
    if(kk == 0)
    {
        len3 = max(a[bb][0],a[bb][1]);
        len4 = min(a[bb][0],a[bb][1]);
    }
    if(kk == 1)
    {
        len3 = max(a[bb][0],a[bb][2]);
        len4 = min(a[bb][0],a[bb][2]);
    }
    if(kk == 2)
    {
        len3 = max(a[bb][2],a[bb][1]);
        len4 = min(a[bb][2],a[bb][1]);
    }
   
    if(len1 >= len3 && len2 >= len4)
        return true;
    else
        return false;
}

int solve(int i,int a,int b,int k)
{
    if(dp[i][a][b][k] != -1) //记忆化搜索
        return dp[i][a][b][k];
    if(a == n && i != m) //剪枝.木块已经使用完了还没达到要求.
        return -1;
    int ans;
    ans = solve(i,a+1,b,k); //不是用当前块,任何时候都成立.
   
    if(i < m) //新起一堆
    {
        for(int kk = 0; kk < 3; ++kk)   
        ans = max( ans , solve(i+1,a+1,a+1,kk)+getH(a+1,kk));
    }
   
    if(i > 0) //加再当前堆上面
    {
        for(int kk = 0; kk < 3; ++kk)
        {
            if(canput(b,k,a+1,kk))
            {
                ans = max( ans , solve(i,a+1,a+1,kk)+getH(a+1,kk) );
            }
        }
    }
    dp[i][a][b][k] = ans;
    return ans;
}

int main()
{
//    freopen("input.txt","r",stdin);
    while(scanf("%d %d",&n,&m) != EOF)
    {
        for(int i = 1; i <= n; ++i)
            scanf("%d %d %d",&a[i][0],&a[i][1],&a[i][2]);
        memset(dp,-1,sizeof(dp));
        for(int i = 1; i <= n; ++i)
        {
            for(int j = 0; j < 3; ++j)
                dp[m][n][i][j] = 0;
        }
       
        int result = solve(0,0,0,0);
        printf("%d\n",result);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值