洛谷-合唱队-java(区间dp)

题目描述

为了在即将到来的晚会上有更好的演出效果,作为 AAA 合唱队负责人的小 A 需要将合唱队的人根据他们的身高排出一个队形。假定合唱队一共 �n 个人,第 �i 个人的身高为 ℎ�hi​ 米(1000≤ℎ�≤20001000≤hi​≤2000),并已知任何两个人的身高都不同。假定最终排出的队形是 �A 个人站成一排,为了简化问题,小 A 想出了如下排队的方式:他让所有的人先按任意顺序站成一个初始队形,然后从左到右按以下原则依次将每个人插入最终棑排出的队形中:

  • 第一个人直接插入空的当前队形中。

  • 对从第二个人开始的每个人,如果他比前面那个人高(ℎh 较大),那么将他插入当前队形的最右边。如果他比前面那个人矮(ℎh 较小),那么将他插入当前队形的最左边。

当 �n 个人全部插入当前队形后便获得最终排出的队形。

例如,有 66 个人站成一个初始队形,身高依次为 1850,1900,1700,1650,1800,17501850,1900,1700,1650,1800,1750,
那么小 A 会按以下步骤获得最终排出的队形:

  • 18501850。

  • 1850,19001850,1900,因为 1900>18501900>1850。

  • 1700,1850,19001700,1850,1900,因为 1700<19001700<1900。

  • 1650,1700,1850,19001650,1700,1850,1900,因为 1650<17001650<1700。

  • 1650,1700,1850,1900,18001650,1700,1850,1900,1800,因为 1800>16501800>1650。

  • 1750,1650,1700,1850,1900,18001750,1650,1700,1850,1900,1800,因为 1750<18001750<1800。

因此,最终排出的队形是 1750,1650,1700,1850,1900,18001750,1650,1700,1850,1900,1800。

小 A 心中有一个理想队形,他想知道多少种初始队形可以获得理想的队形。

请求出答案对 1965082719650827 取模的值。

输入格式

第一行一个整数 �n。
第二行 �n 个整数,表示小 A 心中的理想队形。

输出格式

输出一行一个整数,表示答案  mod 19650827mod19650827 的值。

输入输出样例

输入 #1复制

4
1701 1702 1703 1704

输出 #1复制

8

说明/提示

对于 30%30% 的数据,�≤100n≤100。
对于 100%100% 的数据,�≤1000n≤1000,1000≤ℎ�≤20001000≤hi​≤2000。

思路:

使用区间DP求解,对于队列1701 1702 1703 1704,对于1704后放的种数应该是:队列 1701 1702 1703中 1701 后放的情况(条件:1704 > 1701)+ 队列 1701 1702 1703中 1703 后放的情况(条件:1704 > 1703)都是有判断条件的,而判断条件是队列的一前一后(或是一左一右)两个元素的大小。因此,一个元素进行排队时有两种可能,该元素的前一个元素到底是大于前前一个元素而放在右边,还是小于前前一个元素而放在左边的。通俗来讲就是该元素到底是大于最左边的元素而插入到最右边的,还是大于最右边前一个元素而插入到最右边的。

设队列为i、i+1、i+2、...、j-1、j(区间队列,因此是从i开始,而不是从1开始,逐渐迭代到插入的第一个元素),因此状态转移方程为:

小于插入左边:f[j][i] = f[i + 1][j] (arr[i] < arr[j]) + f[j][i + 1] (arr[i] < arr[i + 1])
大于插入右边:f[i][j] = f[i][j - 1] (arr[j] > arr[j - 1]) + f[j - 1][i] (arr[j] > arr[i])

比如:1 2 3这个理想队列,那可以排列成该队列的第一位只能是1或3(要么插入左边,要么插入右边),则可能的队列为:

以1为第一位

1 2 3 (2为第二位)

1 3 2 (3为第二位)

以3为第一位

3 1 2 (1为第二位)

3 2 1 (2为第二位)

因为代码实现其实很简单,因此建议看到这可以去试试自己写出代码,实在写不出再看下面的代码。

全AC代码:

import java.util.Scanner;

public class Main {
        public static void main(String[] args){
                Scanner scanner = new Scanner(System.in);
                int n = scanner.nextInt();
                int[] arr = new int[1000];
                int[][] dp = new int[1000][1000];

                for(int i = 0; i < n; i++){
                        arr[i] = scanner.nextInt();
                        dp[i][i] = 1;
                }

                for(int i = 1;i < n;i++){
                        for(int j = 0;i + j < n;j++){
                                int k = i + j;
                                if(arr[j] < arr[j + 1]){
                                        dp[k][j] += dp[k][j + 1];
                                }
                                if(arr[j] < arr[k] && j != k - 1){
                                        dp[k][j] += dp[j + 1][k];
                                }
                                if(arr[k] > arr[k - 1]){
                                        dp[j][k] += dp[j][k - 1];
                                }
                                if(arr[k] > arr[j] && j != k - 1){
                                        dp[j][k] += dp[k - 1][j];
                                }
                                dp[j][k] %= 19650827;
                                dp[k][j] %= 19650827;
                        }
                }

                System.out.println((dp[0][n - 1] + dp[n - 1][0]) % 19650827);
        }

如果 arr[j] < arr[j + 1],说明当前子数组的起始元素小于下一个元素,则在已知的 dp[k][j + 1] 的基础上增加一个符合条件的子数组。因此, dp[k][j] 增加 dp[k][j + 1]。

如果 arr[j] < arr[k] 且 j != k - 1,说明当前子数组的起始元素小于末尾元素,则在已知的 dp[j + 1][k] 的基础上增加一个符合条件的子数组。因此, dp[k][j] 增加 dp[j + 1][k]。

如果 arr[k] > arr[k - 1],说明当前子数组的末尾元素大于前一个元素,则在已知的 dp[j][k - 1] 的基础上增加一个符合条件的子数组。因此,将dp[j][k] 增加 dp[j][k - 1]。

如果 arr[k] > arr[j] 且 j != k - 1,说明当前子数组的末尾元素大于起始元素,则在已知的 dp[k - 1][j] 的基础上增加一个符合条件的子数组。因此,将dp[j][k] 增加 dp[k - 1][j]。

ps:不知道为啥洛谷上的测试点竟然没有输入为1的,毕竟一个人是直接入队,哪里有参照物向左插入还是向右插入。

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值