27.方向标

题目

描述

一位木匠收到了一个木制指示牌的订单。每块木板必须与前一块垂直对齐,要么与前一个箭头的基部对齐,要么与相反的一侧对齐,在那里用特制的螺钉固定。两块木板必须重叠。木匠将设计师发送的草图编码成了一个整数序列,但该序列不能确定唯一的模型,他已经把原始草图扔掉了。这看起来像是一个微不足道的任务,但对他来说却是一个巨大的拼图。

序列(有1 + N个元素)将(N)个箭头从底部到顶部的位置进行编码。第一个元素是底部箭头左侧的位置。剩余的N个元素定义了箭头头部的起始位置,从底部到顶部:第i个元素是第i个箭头基部的位置。例如,左侧和右侧的两个标志可以通过2 6 5 1 4进行编码。

由于木板必须与前一块垂直对齐(要么与前一个箭头的基部对齐,要么与相反的一侧对齐),如果序列是2 6 5 1 4 3,第五个箭头可以用一个螺钉固定(在任何一个描绘的标志上)在1(指向右边)或4(指向左边),箭头基座在3。

如果序列是2 3 1,第二个箭头只能用一个螺钉固定在3上,指向左边,因为连续的木板必须重叠。

所有的箭头头部都是相似的,设计师告诉木匠,它们的基部站在不同的垂直线上,底部箭头的左侧也是如此,共同形成1..(N +1)的排列。这就是为什么木匠忽略了细节,只是写下了排列(例如2 6 5 1 4 3)。

给定木匠写下的数字序列,计算可以制作的指示牌数量。由于数字可能非常大,你必须对2147483647取模。序列的第二个整数始终大于第一个整数(底部的箭头总是指向右边)。

输入

第一行有一个整数N,第二行包含从1到N + 1的整数的排列。同一行中的整数之间用一个空格分隔。

输出

输出一行,其中包含给定排列描述的不同标志的数量(对2147483647取模)。

注意

1 ≤ N ≤ 2000


题意

  1. 只需关注箭头长方形部分的头部(靠近三角形)和底部。
  2. 箭头从下往上依次放置。
  3. 第一个箭头的头部和底部题目已经确定,并且头部下标一定大于底部。
  4. 上面的箭头的底部,必须等于下面的箭头的底部,或者等于下面的箭头的头部。
  5. 相邻两个箭头需要有重叠部分,即不能错开。

由此推知

  1. 对于当下箭头而言,上一块箭头的头部和底部很重要。
  2. 上面的箭头的底部可能有多种放法
  3. 题目就是要我们输出有多少种不同的情况。
  4. 要求对输出结果%2147483647。
     

思路(动态规划)

我们用数组s[N+1]记录每一块木块的头部下标。注意:s[0]存储第一块的底部下标

第一步:确定dp数组以及下标的含义

我们需要一个二维dp数组,dp[i][j]表示第i个箭头以j为底时的方案数。

第二步:确定递推公式

3种情况:

  • 第i块的头比上一块的头和尾都小
    dp[i][max(s[i - 1], s[j])] += dp[i - 1][s[j]];
  • 第i块的头比上一块的头和尾都大
     dp[i][min(s[i - 1], s[j])] += dp[i - 1][s[j]]; 
  • 第i块的头在上一块头尾之间
    dp[i][s[i - 1]] += dp[i - 1][s[j]];
    dp[i][s[j]] += dp[i - 1][s[j]];

第三步:dp数组初始化

第一个箭头只有一种放的方法:dp[1][s[1]]=1

遍历每一个箭头时,令dp[i][s[i]]=1,以本身头部为底时为一。结果记得减一

第四步:确定遍历顺序

外循环从第二个箭头开始,内循环遍历上一个箭头所有可能底部。
如果dp[i-1][j]>0,说明上一个箭头存在以该底部放置的方案,方可进入递推。

第五步:结果处理

遍历dp[N],把每一个底部的方案加起来就是结果。


注意事项

  1. long long存储数据。
  2. 最后结果和每次dp变化之后和要取模

C++完整代码

改改再提交乐学,有查重!!

#include <iostream>
#include <vector>
using namespace std;

const int MOD = 2147483647;

int main() {
    int N;
    cin >> N;
    vector<long long> s(N + 1);

    for (int i = 0; i <= N; i++) {
        cin >> s[i];
    }

    vector<vector<long long>> dp(N + 1, vector<long long>(2001, 0));

    dp[1][s[0]] = 1;
    dp[1][s[1]] = 1;

    for (int i = 2; i <= N; i++) {
        for (int j = i - 2; j >= 0; j--) {
            dp[i][s[i]] = 1;
            if (dp[i - 1][s[j]] != 0) {
                if (s[i] > s[i - 1] && s[i] > s[j]) {
                    dp[i][min(s[i - 1], s[j])] += dp[i - 1][ s[j]];
                    dp[i][min(s[i - 1], s[j])] %= MOD;
                }
                else if (s[i] <s[i - 1] && s[i] < s[j]) {
                    dp[i][max(s[i - 1], s[j])] += dp[i - 1][s[j]];
                    dp[i][max(s[i - 1], s[j])] %= MOD;
                }
                else if ((s[i] <= s[i - 1] && s[i] >= s[j]) || (s[i] >= s[i - 1] && s[i] <= s[j])) {
                    dp[i][s[i - 1]] += dp[i - 1][s[j]];
                    dp[i][s[i - 1]] %= MOD;
                    dp[i][s[j]] += dp[i - 1][s[j]];
                    dp[i][s[j]] %= MOD;
                }
            }
        }
    }

    long long res = 0;
    for (int i = 0; i <= 2000; i++) {
        res += dp[N][i];
        res %= MOD;
    }
    cout << res-1 << endl;

    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

榆榆欸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值