zoukankan      html  css  js  c++  java
  • 【题解】反转子序列 | Subsequence Reversal

    题目

    题目来源:[USACO17JAN] P,USACO 2017 January Contest, Platinum;20200502 模拟赛 T3。

    测试地址:LG3607

    题目描述

    FJ 要给他的 (N) 头奶牛拍照,现在 (N) 头奶牛排成一条直线,第 (i) 头奶牛的身高为 (a_i)。在 FJ 的审美里,递增的子序列是最好看的。现在他有一次机会,挑选任意一个子序列,将整个子序列翻转。

    比如奶牛最初的顺序是:(left<1,6,2,3,4,3,5,3,4 ight>)

    FJ 挑选了一个子序列:(left<1,underline{6},2,3,4,3,underline{5},underline{3},underline{4} ight>),可以得到:(left<1,underline{4},2,3,4,3,underline{3},underline{5},underline{6} ight>)

    在只能反转一次任意子序列的情况下,请找到不下降子序列的最大可能长度。

    输入格式

    第一行输入数字 (N)

    接下来 (N) 行,按顺序输入每头奶牛的身高。

    输出格式

    输出反转一次任意子序列后所得到的不下降子序列的最大可能长度。

    评测限制 and 数据范围

    评测时间限制 (1000 extrm{ms}),空间限制 (128 extrm{MiB})

    对于所有数据,(1le Nle 50)。保证 (1le a_ile 50)

    分析

    题目大意是说,给你一个数列,可以反转一个子序列,使得最长不降子序列最大。

    注意到反转的是子序列而不是子串,联系其不连续性,我们就可以将其视为若干对交换并两两包含,并考虑 DP。

    定义

    (f_{i,j}) 为最后一对交换为 (a_i)(a_j) 时在 ([i,j]) 内最长不降子序列长度。但是这个定义无法确定下一项/上一项,所以无法转移。

    如果我们加上左右区间端点(也可以称为「值域」),就可以很好地转移。也就是定义 (f_{i,j,l,r}) 为最后一对交换为 (a_i)(a_j) 时在 ([i,j]) 内、值均在 ([l,r]) 内时的最长不降子序列长度。

    转移

    (为方便讲述,下定义 ([ extrm{Pred}]) 为当 ( extrm{Pred}) 为真时,([ extrm{Pred}])(1),反之为 (0)。)

    首先,单纯增大值域是可以转移的,就是说 (f_{i,j,l,r} = max{f_{i,j,l+1,r},f_{i,j,l,r-1}})

    同时,考虑到要不断扩张不降子序列(以及不是所有的数都在子序列里),所以 (f_{i,j,l,r} = max{f{i+1,j,l,r}+[a_i=l],f{i,j-1,l,r}+[a_j=r]})。(因为是不降序列,所以不用扩张值域)

    最后,还要加上反转操作,所以 (f_{i,j,l,r} = max{f_{i-1,j+1,l,r}+[a_i=r]+[a_j=l]})

    边界

    还有一点要注意的是 DP 的边界问题。

    很显然,(f_{i,i,a_i,a_i}=1),其余为 (0)。最终要求的是 (f_{1,n,1,maxlimits_{small{1le ile N}}{a_j}})

    但可能是因为实现问题,这样的算法在求解类似 (left<1,2,3,4,5 ight>) 时会挂,所以初始设置时要所有 (f_{i,i,1,maxlimits_{small{1le ile N}}{a_j}}) 都为 (1)

    这样,我们就可以愉快地 Coding 了,复杂度 (Theta(N^4)sim 50^4=6.25 imes 10^6),还是 hold 住的。

    Code

    除此以外,就是纯粹的代码了,还是很简单的。

    #include <cstdio>
    using namespace std;
    
    const int max_n = 50;
    
    int dp[max_n][max_n][max_n+1][max_n+1] = {}, a[max_n];
    
    void upd(int& a, int b) { a = ((a > b)? a:b); }
    
    int main()
    {
    	int n;
    	
    	scanf("%d", &n);
    	for (int i = 0; i < n; i++)
    		scanf("%d", a + i);
    	
    	for (int i = 0; i < n; i++)
    		for (int j = 1; j <= a[i]; j++)
    			for (int k = a[i]; k <= max_n; k++)
    				dp[i][i][j][k] = 1;
    	
    	for (int l1 = 2; l1 <= n; l1++)
    		for (int l = 0, r = l1 - 1; r < n; l++, r++)
    			for (int l2 = 1; l2 <= max_n; l2++)
    				for (int lp = 1, rp = l2; rp <= max_n; lp++, rp++)
    				{
    					if (lp != max_n)
    						upd(dp[l][r][lp][rp], dp[l][r][lp+1][rp]);
    					upd(dp[l][r][lp][rp], dp[l][r][lp][rp-1]);
    					
    					upd(dp[l][r][lp][rp], dp[l+1][r][lp][rp] + (lp == a[l]));
    					upd(dp[l][r][lp][rp], dp[l][r-1][lp][rp] + (rp == a[r]));
    					
    					upd(dp[l][r][lp][rp], dp[l+1][r-1][lp][rp] + (lp == a[r]) + (rp == a[l]));
    				}
    	
    	printf("%d
    ", dp[0][n-1][1][max_n]);
    	
    	return 0;
    }
    

    后记

    这道题的指向性很明显,但是还是一道很不错的 DP 练手题。

    当然,我们也可以从这道题中学到「加一维」的思想,也就是说如果当前的 DP 状态无法很好地容纳条件,那么就给 DP 数组加一维。这种思想在以后的 DP 训练或者比赛中是很重要的。

  • 相关阅读:
    scala :: , +:, :+ , ::: , ++ 的区别
    Scala 函数式编程思想
    Scala 关键字
    HDFS 的内存存储是什么?
    LRU
    高并发情况限流
    Java中List集合去除重复数据的方法
    MySQL支持的跨库事务
    死磕ConcurrentHashMap 1.8源码解析
    一致性Hash算法
  • 原文地址:https://www.cnblogs.com/5ab-juruo/p/solution-usaco_201701_p_subsequence_reversal-20200502_subrev.html
Copyright © 2011-2022 走看看