【CF486E】LIS of Sequence题解

【CF486E】LIS of Sequence题解

题目链接

题意:

给你一个长度为n的序列a1,a2,...,an,你需要把这n个元素分成三类:1,2,3:

1:所有的最长上升子序列都不包含这个元素

2:有但非所有的最长上升子序列包含这个元素

3:所有的最长上升子序列都包含这个元素

输入格式:

第一行包含一个正整数n,表示序列的长度。第二行包含n个正整数a1,a2,...,an,表示序列中的元素。

输出格式:

一行,包含一个长度为n的、由1,2,3三种数字组成的字符串,第i个数字表示ai所属类别。

样例输入1:

1
4

样例输出1:

3

样例输入2:

4
1 3 2 5

样例输出2:

3223

样例输入3:

4
1 5 2 3

样例输出3:

3133

时空限制:

每个测试点2s,256MB

数据范围:

1≤n≤105

1≤ai≤105

题解:

首先,O(nlogn)求LIS大家应该都会吧……这里就不阐述了。

我们可以通过DP来算出以每个点i结尾的最长上升子序列的长度,记为length[i],把整个序列的LIS长度记为len。

然后,大家要想明白一个命题:

如果元素i可能出现在LIS中,那么它在LIS中的位置一定刚好等于length[i]。

这个自证不难。

其次,我们可以用一个“分层数组”,其实所谓的“分层数组”就是一个二维数组,只不过我习惯把第一维叫做“层”罢了,第二维因为节省空间,我们开一个vector。

这个“分层数组”相当重要,它是我们解决问题的核心。我们在DP时就已经算出每个元素的length[i]了,因此我们把这个元素的编号添加到层数为length[i]的vector中去,因此“分层数组”中每一层的每个点就和序列中的元素一一对应。

这就是“分层数组”的建立过程,可参见代码注释。

然后呢?

大家又需要想明白两个命题:

在每一层中,随着编号的递增,与点对应的元素的值单调不递增。

每一层(除第一层外)中的每个点一定在前一层中有编号小于它且序列元素值小于它的节点。

考虑我们DP的过程,我们在更新dp数组中下标为length[i]的最小值时,如果已经通过二分查找算出该元素的length[i],元素可以更新dp[length[i]]的条件是元素的值一定不能比dp[length[i]]大,而dp[length[i]]已经通过前面的元素算出,因此在“分层数组”层数为length[i]的层中,该元素的值一定小于等于在该层中编号比它小的元素的值。

另外,我们在DP时,依据dp数组单调递增的特性可以理解dp[length[i]-1]的值一定比a[i]小,因此在“分层数组”的前一层中,一定有编号小于它且序列元素值小于它的点,这样保证我们在逐层往前推的时候不会出现“一对空”的情况,而是在前一层中是有一个区间(包含编号、序列元素值两方面的限制)与之对应。这个区间中的点就是当前点在前一层中可以扩展到的节点。

大家可能有疑问:为什么要倒着从后往前推呢?

因为倒着推能够保证要扩展的当前点一定有LIS经过它,因此它扩展到的前一层的节点也一定有LIS经过,等到该层所有点扩展完毕后,前一层没有被扩展到的点一定没有LIS经过,打上“1”类标记,下次扩展就不要扩展这些点了。而其他的点都打上“2”类标记,如果只有一个“2”类标记,那么把“2”改成“3”,说明该节点是所有LIS的必经节点。

参考代码如下(若有不理解的参看注释):

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<vector>
 5 #define maxn 100005
 6 using namespace std;
 7 int n,a[maxn];//整个序列
 8 int dp[maxn];//dp[i]代表当前长度为i的上升子序列末尾元素的最小值
 9 int len=0;//整个序列LIS的长度
10 int sign[maxn];//序列元素所属类别
11 vector<int>layer[maxn];//分层数组,一共有len层。layer[i]中的点表示这些元素:以这些元素结尾的最长上升子序列长度为i
12 inline int bins(int i,int val){
13     int l=0,r=layer[i].size()-1;
14     while(l<r){
15         int m=(l+r)>>1;
16         if(a[layer[i][m]]>=val)l=m+1;
17         else r=m;
18     }
19     return l;
20 }
21 int main(){
22     scanf("%d",&n);
23     memset(dp,0x3f,sizeof(dp));
24     dp[0]=0;
25     for(int i=1;i<=n;i++){
26         scanf("%d",&a[i]);
27         int length=lower_bound(dp,dp+n+1,a[i])-dp;//O(nlogn)求LIS
28         dp[length]=min(dp[length],a[i]);
29         //length表示以该元素结尾的最长上升子序列的长度
30         layer[length].push_back(i);//加入到分层数组
31         len=max(len,length);//更新整个序列的LIS长度
32     }
33     for(int i=1;i<=n;i++)sign[i]=1;//初始化全都为第1类,即任何LIS都不经过它们
34     if(layer[len].size()==1)sign[layer[len][0]]=3;
35     else for(int i=0;i<layer[len].size();i++)sign[layer[len][i]]=2;
36     for(int i=len;i>=2;i--){//倒序处理分层数组,一层一层往前推
37         for(int j=0;j<layer[i].size();j++){//枚举当前层的所有点
38             int bh=layer[i][j];//点的编号
39             if(sign[bh]>1){//如果当前节点可以向前扩展(存在LIS经过当前点)
40                 int l=bins(i-1,a[bh]);//二分查找,扩展的节点在序列中的值必须小于当前节点,才能保证LIS严格递增
41                 int r=lower_bound(layer[i-1].begin(),layer[i-1].end(),bh)-layer[i-1].begin()-1;//二分查找,扩展的点编号必须小于当前点编号,才能是“序列”
42                 //当前点可扩展到的前一层的点的范围是区间[l,r]
43                 for(int k=l;k<=r;k++)sign[layer[i-1][k]]=2;//打上标记,该节点能够被扩展到说明一定在整个序列中有某个LIS包含该点
44             }
45         }
46         //当前层能够扩展到的前一层的点是当前层所有点能扩展到的前一层的节点的并集
47         int cnt=0,pos=0;
48         for(int j=0;j<layer[i-1].size();j++)if(sign[layer[i-1][j]]==2){
49             cnt++;
50             pos=j;
51         }
52         if(cnt==1)sign[layer[i-1][pos]]=3;//如果该层所有可扩展的点只能在前一层中扩展出一个节点,说明这个节点是所有LIS的必经节点。
53     }
54     for(int i=1;i<=n;i++)printf("%d",sign[i]);//不留空格打印
55     return 0;
56 }

原文地址:https://www.cnblogs.com/AndyGamma/p/9814368.html

时间: 2024-10-11 08:18:39

【CF486E】LIS of Sequence题解的相关文章

Codeforces 486E LIS of Sequence 题解

题目大意: 一个序列,问其中每一个元素是否为所有最长上升子序列中的元素或是几个但不是所有最长上升子序列中的元素或一个最长上升子序列都不是. 思路: 求以每一个元素为开头和结尾的最长上升子序列长度,若两者相加比最长上升子序列长度+1小,则一个也不是:否则若有另一元素与它的两个值完全相同,则不是所有;否则在所有. 代码: 1 #include<map> 2 #include<cstdio> 3 #include<algorithm> 4 using namespace st

POJ 1019 Number Sequence 题解

这又是一道看似简单,实际挺困难的题目. 本来想做道基础题消遣一下的,没想到反被消遣了-_-|||. 看个人的基础吧,对于数学好的会简单点,但是由于情况太多,需要都考虑全,故此难度应该在4星以上了. 我这里使用的方法就是直接打表,然后直接模拟,利用打表去掉一大段数据,剩下数据量十分小了,故此可以直接模拟. 打表是为了计算前面的周期数,把周期数直接去掉. 主要难点是后面10位数以上的数有2位, 3位,4位等情况要考虑.- 下面使用getNewNums一个函数解决了,想通了,就几行代码,还不用难理解的

TopCoder SRM 625 Incrementing Sequence 题解

本题就是给出一个数k和一个数组,包括N个元素,通过每次增加数组中的一个数的操作,最后需要得到1 - N的一个序列,不用排序. 可以从暴力法入手,然后优化. 这里利用hash表进行优化,最终得到时间效率是O(n*n)的算法,而且常数项应该很低,速度还挺快的. 思路: 1 如果数组A[i]在1 -N 范围内,就利用bool B[]记录,这个数已经找到了: 2 如果A[i]的值之前已经找到了,那么就增加k操作,得到新的值A[i]+k,看这个值是否找到了,如果没找到,就使用B记录,如果之前已经找到了,那

Codeforces Round #277 E. LIS of Sequence(486E) 树状数组乱搞

http://codeforces.com/contest/486/problem/E E. LIS of Sequence time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standard output The next "Data Structures and Algorithms" lesson will be about Longest I

Codeforces 486E LIS of Sequence(线段树+LIS)

题目链接:Codeforces 486E LIS of Sequence 题目大意:给定一个数组,现在要确定每个位置上的数属于哪一种类型. 解题思路:先求出每个位置选的情况下的最长LIS,因为开始的想法,所以求LIS直接用线段树写了,没有改,可以用 log(n)的算法直接求也是可以的.然后在从后向前做一次类似LIS,每次判断A[i]是否小于f[dp[i]+1],这样就可以确定该位 置是否属于LIS序列.然后为第三类的则说明dp[i] = k的只有一个满足. #include <cstdio>

486E - LIS of Sequence(LIS)

题意:给一个长度为n的序列,问每个数关于序列的LIS(longest increasing sequence)是什么角色.这里分了三种: 1.此数没有出现在任意一条LIS中 2.此数出现在至少一条但是不是全部的LIS中 3.此数出现在所有的LIS中 解法:nlgn的LIS算法可以求出以每个i位置结束的LIS长度up[i].出现在LIS的数其实就是一个dag,找出那些某层唯一数值的数就行.LIS算法后,从后向前扫,维护所以长度的最大值,这中间可以判断某长度有几个值,如果某些长度有多个位置则他们都属

CodeForces 486E LIS of Sequence

题意: n(10^5)个数字的序列a  求每个位置i  它是不出现在任何LIS中  还是  出现在一些LIS中  还是  出现在所有LIS中 思路: 比赛时候唯一没做出的题-  赛后还是不会做- - -b  看了别人的代码觉得好精妙!! 首先以O(nlogn)复杂度求出LIS 然后我们倒序扫描序列a  对于位置i  如果lis[i]=LIS或者a[i]<big[lis[i]+1] (big[x]表示lis=x的a的最大值  这里的意思是  如果a[i]是某个LIS的最后一个  或者  能与某个L

Codeforces 486E LIS of Sequence --树状数组

题意: 一个序列可能有多个最长子序列,现在问每个元素是以下三个种类的哪一类: 1.不属于任何一个最长子序列 2.属于其中某些但不是全部最长子序列 3.属于全部最长子序列 解法: 我们先求出dp1[i]表示1~i 的最长递增子序列长度, dp2[i]表示 n~i 的最长递减子序列长度(严格增减),这里我们可以用维护最大值的树状数组来解决,开始还以为要用nlogn求LIS的那种算法,当然那样应该也可以,这里元素值是1~10^5的,可以直接用树状数组,如果元素值任意的话,我们离散化一下也可以用树状数组

UVa 10534 DP LIS Wavio Sequence

两边算一下LIS就出来了,因为数据比较大,所以需要二分优化一下. 1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 using namespace std; 6 7 const int maxn = 10000 + 10; 8 9 int n; 10 11 int a[maxn], l[maxn], r[maxn]; 12 int g