题目:从一个由N个整数排列组成的整数序列中,自左向右不连续的选出一组整数,可以组成一个单调减小的子序列(如从{68 69 54 64 68 64 70 67 78 62 98 87}中我们可以选取出{69 68 64 62}这个子序列;当然,这里还有很多其他符合条件的子序列)。给定整数序列的长度和整数序列中依次的值,请你求出这个整数序列中“最长的单调减小的子序列的长度”以及“不同但长度都是最长得单调减小的子序列的数量”。

   输入第1行为一个整数N,表示输入的整数序列的长度(1≤N≤50000)。输入第2行包括由空格分隔的N个整数(每个整数都在32位长整型范围内)。

输出包括一行,为两个数字,分别为针对给定的整数序列求出的“最长的单调减小的子序列的长度”以及“值不同但长度都是最长得单调减小的子序列的数量”
样例输入
12
68 69 54 64 68 64 70 67 78 62 98 87
样例输出
4 2


对于这个题,一共有两个小部分的问题要解决。前一个问题是最长不上升子序列,属于LIS问题,使用动态规划解决,后一个问题属于去重问题。
对于LIS问题,声明dp[i] 以第i个元素为结尾的子序列的最长的长度。
对第i个元素,与前i-1个元素进行比较:
dp[i] = 1; //当末尾只要一个元素时 长度为1
如果 arr[i] < arr[j]:

如果dp[i] < dp[j] + 1
此时dp[i]的值会被更新为dp[j] + 1

其他情况不做处理

对于去重问题:
“值不同但长度都是最长得单调减小的子序列的数量” 这里说的是:

比如输入:
6
2 1 2 1 2 1
输出应为 2 1

2 1 2 1 这两个是值相同的,所以应该当做一个
使用size[i] 数组去记录第i元素为结尾时,值不同但长度都是最长得单调减小的子序列的数量
每次在dp更新一遍以后,进行size的更新。
去掉相同值的情况,如果只去关注最后结尾时:
因为每次遍历都会更新状态,也就是说如果有相同值的时候 后者会把前者的情况 都会过一遍,所以只要每次更新时保证只取相同值的最后一个出现的元素位置的size[j]即可,也就是最右边的那个。
对于i元素所构成的最长子序列的前一个元素可能有很多不同值,所以要记录这些值,并只取最右边的。
最后size 和 dp都已经生成了最终数组
然后对整个数组进行遍历, 找出最大序列 且值不同的序列的数量
方法同找单个i位置元素的值不同但长度都是最长得单调减小的子序列的数量 一致
其他说明:
数据较大 使用java中的BigInteger
遍历找值不同但长度都是最长得单调减小的子序列的数量时 使用倒序查找

代码:

Scanner read = new Scanner(System.in);
        int n = read.nextInt();
        long[] arr = new long[n];
        long[] dp = new long[n];
        BigInteger[] size = new BigInteger[n];
        for(int i = 0; i < n; ++i){
            arr[i] = read.nextLong();
        }
        long max = 0;
        
        for(int i = 0; i < n; ++i){
            dp[i] = 1;
            size[i] = new BigInteger("0");
            for(int j = 0; j < i; ++j){
                if(arr[j] > arr[i]){
                    if(dp[j] + 1 > dp[i]){
                        dp[i] = dp[j] + 1;
                    }
                }
            }
            if(dp[i] > max){
                //更新 最长长度
                max = dp[i];
            }
            // 确定以arr[i]结尾的 子序列中 值不同但长度都是最长得单调减小的子序列的数量
            if(dp[i] > 1){//如果  不是只有一个数字的时候
                Set<Long> sl = new HashSet<>();
                for(int j = i - 1; j >= 0; --j){
                    //从右向左查询  只查询第一次遇到的并且是最大长度的 size[i]
                    // 没有记录路径 通过 arr[j] > arr[i] && dp[j] == dp[i] - 1 来确定是否是前一个转移
                    // 遇到相同结尾的情况,更右边的已经包含了左边的情况
                    if(arr[j] > arr[i] && dp[j] == dp[i] - 1 && !sl.contains(arr[j])){
                        sl.add(arr[j]);//去重
                        size[i] = size[i].add(size[j]);
                    }
                }
            }else{
                //只有一个数字是 数量为1
                size[i] = new BigInteger("1");
            }
        }
        BigInteger maxBigI = new BigInteger("0");
        Set<Long> set = new HashSet<>();
        //遍历整个序列  找出最大长度 且值不同的序列的数量
        for(int i = n - 1; i >= 0; --i){
            if(dp[i] == max && !set.contains(arr[i])){
                set.add(arr[i]);
                maxBigI = maxBigI.add(size[i]);
            }
        }
        System.out.println(max + " " + maxBigI.toString());
    }

可达鸭
3 声望1 粉丝

学会闭嘴