Lis问题
动态规划解决LIS问题
给定一个序列a1,a2,…,an,找出其中最长的一段子串b1,b2,…,bk,使得该子串严格递增。
这个问题的解决方法非常多,比如说用动态规划,定义dp(i)表示以第ai结尾的子串中最长的子串长度,那么很显然有递推公式:
dp(i)=maxj<i∧aj<aidp(j)这个转移可以用线段树进行优化,优化后的时间复杂度为O(nlog2n)。
讲另外一种二分的做法。定义fi(j)表示仅考虑序列前i个元素,所有长度为j的递增子序列的最后一个元素的最小值。
很显然必定有fi(j)≥fi(j−1),fi(j)≥fi−1(j)。因为存在长度为j的子序列,删除其中任意一个元素,一定可以得到另外一个长度为j−1的子序列。
因此,考虑第i个数ai的时候,我们需要找到最大的j满足,fi−1(j)<ai,由于fi−1是递增函数,因此二分是适用的。我们可以保证fi(j+1)=ai,而对于其余的值t≠j+1,一定有fi(t)=fi−1(t)。因为假如fi(j+1)小于ai,那么一定有fi−1(j+1)=fi(j+1)<ai,这与之前j最大相悖。
平衡树解决LIS问题
平衡树也可以用于解决LIS问题。具体做法就是在平衡树的第k个顶点关键字表示dp(k),即构建长度为k的递增子序列,最后一个数字最小能是多少。那么考虑处理下一个数x的时候。我们需要清理掉关键字大于等于x的最小的顶点,并将x插入到平衡树中。
稍微说一下原因,我们从转移的角度来考虑问题,当遇到x的时候,如果dp(k)<x,则可以尝试将dp(k+1)优化到x。这样我们将dp序列切成两部分,第一部分中所有顶点关键字小于x,其余顶点在第二部分。很显然只有第一部分的最后一个顶点可以转移成功,设l为第一部分的长度,则转移为dp(l+1)=x。考虑第二部分发生的变化,如果第二部分中含有关键字为x的顶点,则插入x无影响,但是我们出于简便考虑,直接将其删除即可。同理如果第二部分没有含有关键字为x的顶点,考虑其中最小的顶点所代表的含义为dp(l+1)>x,我们发现这东西已经没用了,将其删除即可。
TreeSet<Integer> set = new TreeSet();
for(int x : a){
Integer ceil = set.ceiling(x);
if(ceil != null){
set.remove(ceil);
}
set.add(x);
}
很显然平衡树解决LIS的时间复杂度为O(nlog2n)。可以发现平衡树版本的实现更加简单。实际上平衡树版本还能解决LIS的扩展版本问题。
给定n个区间,第i个区间为[li,ri]。要求从每个区间选择一个数,记第i个区间选择的数为xi。于是我们得到了一个序列x1,…,xn,问这个序列的LIS长度。其中n≤106,1≤li≤ri≤1018
我们可以维护相同的一个平衡树,其中每个顶点的含义是相同的。但是现在我们要处理的是区间而非一个点。我们可以将整个平衡树切分成三部分,L中存储关键字小于l的顶点,M中存储关键字落在[l,r−1]中的顶点,而其余顶点存在R中。考虑这三部分会发生怎样的改变,很显然插入这个区间对于L中顶点不会产生影响。而对于M,我们可以在前面插入一个新的顶点l−1,之后将M中的顶点的关键字全部增加1(这个可以用惰性标记)。对于R,由于M增大了1,因此它的第一个顶点,含义是dp(|L|+|M|)≥r,这个顶点可以被删除(因为和M中最后一个顶点冲突)。
最后的结果就是平衡树中有多少顶点。总的时间复杂度为O(nlog2n)。
最长递增子序列和最小递减序列划分
如果我们可以将序列划分为若干个子序列,每个子序列都严格递减,那么称这是一个递减序列划分。所有递减序列划分中,划分的子序列最少的称为最小递减序列划分。
定理:一个序列的最长非严格递增子序列长度等于该序列的最小递减序列划分大小
首先我们建立dp[i],表示所有长度为i的递增序列的最小末端元素,很显然在用二分法优化DP的时候dp[i]是不断递减的,而有意义的dp仅为dp[1],dp[2],…,dp[m],其中m是LIS的长度。因此我们可以发现对于固定的k,加入到dp[k]中的元素构成了一个递减子序列,而这些m个子序列构成了原来序列的一个划分。
这样我们就证明了划分的存在,现在我们证明不可能存在更小的划分。实际上,考虑一个LIS对应的序列b1,b2,…,bm,容易发现任意两个元素不能处于同一个递减子序列中,因此递减子序列数至少为m。
引理:一个序列的最长非严格递减子序列长度等于该序列的最小递增序列划分
置换的递增/递减子序列分解
对于1,2,…,n的所有置换,我们希望将序列分解为最少的一些子序列,要求这些子序列满足递增或递减性质(分解的不同子串可以分别满足递增和递减)。考虑1,2,…,n的所有置换,它们中最小分解的最大值f(n)是多少。
可以证明这个值是最小的正整数k,满足k(k+1)2≥n。
用归纳法证明,当n=1的时候,k=1,命题一定成立。
考虑任意n,假设其LIS长度大于等于k,那么我们就可以从中提取出LIS作为一个划分的一部分,于是最少划分数量为f(n−k)+1,此时有k(k+1)2≥n⇒k(k−1)2≥n−k。因此f(n−k)=k−1,故f(n)=k。
那么如果LIS长度小于等于k呢,由于最小递减子序列划分数等于最长递增子序列长度,因此我们可以正好将整个序列划分为k个递减子序列。
LIS的期望长度
题目1:给定n个未知数x1,…,xn,其中已知xi在范围[li,ri]中均匀分布,且每个未知数都是独立的。现在要求计算序列x1,…,xn的最长严格递增子序列的长度的期望。其中1≤n≤8。
提供一道题目。
一般计算LIS是没法和期望一起算的,因为不能和期望的线性性质结合。具体的做法就是暴力枚举所有可能的排列,从而摆脱需要计算LIS带来的影响。总共有n!种排列,排列p1,…,pn的意思是将所有未知数从小到大排列后的结果,如果xi=xj,则按照i,j的大小倒向排序。
在已知排列信息的前提下,可以发现序列x1,…,xn的LIS对应于p1,…,pn的LIS。
接下来问题就变成了已知xp1≤xp2<xp3…且li≤xi≤ri,问有多少种方案。这个是经典问题,做法就是可以发现未知数的取值区间可以划分为O(n)个连续段,每个未知数在每个段上要么都能取到,要么都不能取到。之后记dp(i,j)表示在前i个连续段上分配j个未知数的方案数。这个可以O(n4)计算的到。
时间复杂度为O(n!n4)。
构造递增递减子序列
题目1:给定n,a,b,要求构建一个1到n的排列,其中最长递增子序列的长度为a,而最长递减子序列的长度为b,要求输出这样的一个序列,或则报告无解。其中1≤n,a,b≤105。
首先由于任意递增序列和递减序列最多有一个交点,因此n≥a+b−1是必要条件。下面我们证明n≤ab也是必要条件。考虑一个满足条件的排列,记f(i)表示以i结尾的最长递增子序列的长度,那么对于给定的1≤x≤a,可以发现满足f(i)=x的所有i组成的子序列是递减的,而这实际上将所有的元素划分到了a个分组中。由于每个分组最多有b个元素,因此一定有ab≥n成立。
下面考虑如何构造这样的一个序列。首先我们构造一个长度为ab的序列:b,b−1,…,1,2b,2b−1,…,b+1,3b,…,ab,ab−1,…,(a−1)b+1。可以发现这个序列的最长递增子序列长度为a,最长递减子序列长度为b,我们需要做的就是从前面a−1个递减序列中删除一些元素,使得总数达到n。
一道题目。
LIS的必要点
给定一个序列a1,a2,…,an,我们记best为整个序列的最长递增子序列的长度。那么如果我们从序列中删除元素ai,会导致序列的最长递增子序列长度减少为best−1,则称i为序列的必要点。
下面我们设计一个算法来计算所有必要点。
我们记录R(i)表示以ai作为结尾的最长递增子序列的长度。
一个点是必要点,当且仅当至少一个最长递增子序列包含这个点,并且所有最长递增子序列都包含这个点。后者我们重新表述为可以对于所有至少出现于一个最长递增子序列中的点j,R(i)=R(j)⇒i=j。
我们首先找出有哪些点被最长递增子序列包含。如果i出现在最长递增子序列中,当且仅当存在j>i,且R(i)+1=R(j),且ai<aj,且j出现在最长递增子序列中,或者D(i)=best。我们可以从后往前倒推,可以在线性时间复杂度内得到所有结果。
总的时间瓶颈在计算R的过程中,这个可以直接二分+DP玩成,时间复杂度为O(nlog2n)。
提供一道问题。