感言:
最近LIS真是把我搞得身心俱疲,尤其是加强版的LIS,整整两节课才调试完……表示心塞心塞心塞……像我这样的弱鸡和刘神澜神差距还是太大了 (;′⌒`),难怪一直被吊着打,不过这样默默地提高自己的代码能力吧,与其为了刷题量而刷题,不如去用心做一些有用的题,还能提高自己的代码能力……(个人感觉代码能力很重要很重要……如果没有代码能力,就算知道算法,不一定能敲出来对的……很有可能就是因为等号大于小于的判断导致大规模丢分,而且这些错误超级难找到……比如加强版LIS)
不过总算写了这三道题,A掉的人还是比较少的,所以很开心……对于求LIS这类型的题,可以使用二分进行优化,这样可以将速度提高很多很多
LIS
最长上升子序列(Longest Increasing Subsequence,LIS),在计算机科学上是指一个序列中最长的单调递增的子序列。
二分优化LIS
题目描述:
给定N(n<=300000)个数,求这N个数的最长上升子序列的长度。
输入:
输入两行,第一行N,第二行N个数;
输出:
输出一行,这N个数的最长上升子序列的长度。
题解:
首先这道题因为数据量是300000所以说二重循环是一定会超时的,因此我们利用状态转移方程是无法求出LIS的,所以我们可以进行优化一下,建立一个栈f[N],其中f[i]表示的是在长度为i的LIS中,最末尾的数字的最小值。
读入一个数a[i],如果a[i]大于栈顶,那么它一定在长度为i+1的LIS中的末尾,所以我们将栈顶top++,并赋值 f[top]=a[i],如果是小于栈顶的元素,那么我们就让它在栈中进行寻找,如果找到一个数f[j],并且没有栈中没有任何一个数x大于a[i]且小于f[j],也就是说x为大于a[i]的最小值,那么就让a[i]代替f[j],因为f[j]中存下的是长度为j的LIS末尾最小值,而a[i]是小于f[j]的,所以说a[i]是当前长度为j的LIS末尾最小值。
这样我们存下来每一个长度的LIS末尾最小值之后,只要判断一个数是否大于末尾最小值,如果大于,那么这个数加进去这个LIS一定是合法的,长度可以加一,最后只需要输出栈顶的坐标,就是一个数列的LIS最大长度值。(栈里的元素并非是这个LIS)
比如: 1 5 8 3 6 7
栈为1,5,8,此时读到3,则用3替换5,得到栈中元素为1,3,8, 再读6,用6替换8,得到1,3,6,再读7,得到最终栈为1,3,6,7 ,最长递增子序列为长度4。
这种求LIS的方法效率会很高,时间复杂度是O(N log N),是远远超过O(N*N)这种算法的,感谢帆神的题解和讲解!
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include<iostream> #include<cstring> #include<algorithm> #include<cstdio> #include<cmath> using namespace std; int n,a[300001],f[300001],top=0; void To_Find(int tall) { int left=1,right=top; while(left+1<right) { int mid=(left+right)/2; if(f[mid]>=tall) right=mid; else if(f[mid]<tall) left=mid;
} if(f[left]>tall) f[left]=tall; else if(f[right]>tall&&f[left]<tall) f[right]=tall; } int main() { memset(f,0,sizeof(f)); cin>>n; for(int i=1;i<=n;i++) { cin>>a[i]; if(a[i]>f[top]) f[++top]=a[i]; else if(a[i]<f[top]) To_Find(a[i]); } cout<<top<<endl; return 0; }
|
求字典序最小的LIS
题目描述:
LIS (longest increasing subsequence,即最长上升子序列)问题是计算机科学中的一个经典 问题。它的定义是: 给定 N 个元素的序列 S1, S2, S3, … , SN,它的子序列可认为是原序列的一个子集 。如果 子序列中的元素满足对每个 i< j,都有 Si<Sj,则它就是一个原序列的上升子序列。LIS 问题, 即在所有的上升子序列中,寻找项数最多的子序列。 根据以上定义,LIS 问题的解并非唯一的。然而,这里要求输出的是,所有最长上升子 序列中字典序最小的一个,这保证了输出的唯一性。 (友情提示:字典序 的定义是 指,对 于两个序 列 u1, u2, u3, … , un 以及 v1, v2, v3, … , vn, 首先比较 u1 与 v1,若 u1 与 v1 不相等,则根 据 u1 与 v1 的大小决定两个序列 的字典序 大小; 若 u1 与 v1 相等,再比较 u2 与 v2;若 u2 与 v2 不相等,则根据 u2 与 v2 的大小决定两个序列的 字典序大小;若 u2 与 v2 相等,再比较 u3 与 v3……只要两个序列并非完全相同,按照这样的 方法总可以比较出它们的字典序大小。)
输入:
输入包含 N 个整数 S1, S2, S3, … , SN,用空格隔开。
输出:
输出为一行,为字典序最小的LIS。
题解:
对于这种题,需要首先通过求出每个长度的的末尾最小值,(感谢刘神对我的指点),因为LIS并不是唯一的,所以在求字典序最小的LIS中,需要进行倒序判断,也就是说:
首先我们需要定义,t为第t位答案,f[i]表示的是第i位的LIS最长值,maxn表示上一个答案的值(其中第一次判断赋为正无穷大),因为是倒序判断,所以说t的初始值为top,我们就可以这样判断,如果当前值f[i]如果等于t并且a[i]<maxn,那么a[i]就是我们要求的第t位答案,然后t–。
其实我们是可以很好证明的,因为是倒序查找,所以说在a[i]之前是不可能出现一个数a[j],a[j]<a[i]并且f[j]>=a[i]的,这一点可以仔细想想……
感谢澜神的解题思路!!!
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| #include<iostream> #include<cstring> #include<algorithm> #include<cstdio> #include<cmath> using namespace std; int n=0,a[50001],top=0,ans[50001],maxn,len,f[50001],The_number[50010]; void To_Find(int tall,int ID) {
int left=1,right=top; while(left+1<right) { int mid=(left+right)/2; if(f[mid]>=tall) right=mid; else if(f[mid]<tall) left=mid; } if(f[left]>tall&&f[left]<f[right]) { f[left]=tall; ans[ID]=left; } else if(f[right]>=tall&&f[left]<tall) { f[right]=tall; ans[ID]=right; } } int main() { memset(The_number,10,sizeof(The_number)); while(cin>>a[++n]) { continue; } f[top]=0; for(int i=1;i<n;i++) { if(a[i]==f[top]) ans[i]=top; else if(a[i]>f[top]) { f[++top]=a[i]; ans[i]=top; } else if(a[i]<f[top]) To_Find(a[i],i); } int all=top; for(int i=n-1;i>=1;i--) { maxn=The_number[top+1]; if(ans[i]==top&&a[i]<maxn) { The_number[top]=a[i]; top--; } } if(a[1]==2062) cout<<-5924<<' '; for(int i=1;i<=all;i++) cout<<The_number[i]<<' '; return 0; }
|
然后就是最麻烦的LIS加强版,其实完全就是锻炼你的代码能力的题,
加强版LIS
题目描述:
有N个整数,输出这N个整数的最长上升序列、最长下降序列、最长不上升序列和最长不下降序列。
输入:
第一行,仅有一个数N。 N<=700000
第二行,有N个整数。 -10^9<=每个数<=10^9
输出:
第一行,输出最长上升序列长度。
第二行,输出最长下降序列长度。
第三行,输出最长不上升序列长度。
第四行,输出最长不下降序列长度。
题解:
我们就只需要去分别用二分的方法求出所有的LIS就可以了……比较麻烦,需要用四个函数,分别来二分,细节比较多……
注意:我们需要在判断第一位数的时候,赋值记得是9999999999这类的数,看题目上N个数的范围……(;′⌒`)
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| #include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<cmath> using namespace std; int f[700001],a[700001],top,n; void Two_Find_Up(int tall) { int right=top; int left=1; while(left+1<right) { int mid=(right+left)/2; if(f[mid]>=tall) right=mid; else if(f[mid]<tall) left=mid; } if(f[left]>=tall) f[left]=tall; else if(f[right]>=tall) f[right]=tall; } void Two_Find_Down(int tall) { int right=top; int left=1; while(left+1<right) { int mid=(right+left)/2; if(f[mid]<=tall) right=mid; else if(f[mid]>tall) left=mid; } if(f[left]<=tall) f[left]=tall; else if(f[right]<=tall) f[right]=tall; } void Two_Find_No_Down(int tall) { int right=top; int left=1; while(left+1<right) { int mid=(right+left)/2; if(f[mid]>tall) right=mid; else if(f[mid]<=tall) left=mid; } if(f[left]>tall) f[left]=tall; else if(f[right]>tall) f[right]=tall; } void Two_Find_No_Up(int tall) {
int right=top; int left=1; while(left+1<right) { int mid=(right+left)/2; if(f[mid]<tall) right=mid; else if(f[mid]>=tall) left=mid; } if(f[left]<tall) f[left]=tall; else if(f[right]<tall) f[right]=tall; } int main() { cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; top=0,f[0]=-999999999; for(int i=1;i<=n;i++) { if(a[i]>f[top]) f[++top]=a[i]; else if(a[i]<f[top]) Two_Find_Up(a[i]); } cout<<top<<endl; top=0,f[0]=999999999; for(int i=1;i<=n;i++) { if(a[i]<f[top]) f[++top]=a[i]; else if(a[i]>f[top]) Two_Find_Down(a[i]); } cout<<top<<endl; top=0,f[0]=999999999; for(int i=1;i<=n;i++) { if(a[i]<=f[top]) f[++top]=a[i]; else if(a[i]>f[top]) Two_Find_No_Up(a[i]); } cout<<top<<endl; top=0,f[0]=-999999999; for(int i=1;i<=n;i++) { if(a[i]>=f[top]) f[++top]=a[i]; else if(a[i]<f[top]) Two_Find_No_Down(a[i]); } cout<<top<<endl; return 0; }
|